23 Commits
Author SHA1 Message Date
mcndave 5397ed4fae - First task of v2 done 2023-02-02 16:43:15 +00:00
mcndave 30ddd4c98b - Fixed some bugs related to options 2023-02-02 15:26:26 +00:00
mcndave f00c98b7ff - Some more work done on documentation and fixed some code at PHP 8 level 2023-01-26 13:33:53 +00:00
mcndave 3986437800 - Added documentation, fixed some bugs and optimized some parts of the source code while doing the doc. 2022-12-22 14:34:37 +00:00
mcndave 9190987b53 - Fixed the parse type 2022-12-21 04:24:12 +00:00
mcndave cd3cb18665 Merge branch 'master' of https://git.mcnd.ca/mcndave/picea-ui 2022-11-29 19:48:28 +00:00
mcndave 3a930c13b1 - Removed sprintf which was causing problems with it's own escaping mechanism while using it in a view 2022-11-29 19:48:17 +00:00
Dave Mc Nicoll bd501bce86 - Added the multiple value selection 2022-11-11 13:58:22 +00:00
Dave Mc Nicoll 1f5d38a8b9 - Added some more information into the form context whether a form was sent, and what was it's return value (if there was any) 2022-10-10 15:30:15 +00:00
Dave Mc Nicoll 164a27d267 - Added context validation status 2022-06-27 17:29:16 +00:00
Dave Mc Nicoll ceebc6783a - Added basic handling of BODY content from Web-RestMethod from PS 2022-05-11 14:09:18 +00:00
Dave Mc Nicoll 83fee32ac0 - Added CSRF through form processing 2022-02-25 15:33:40 +00:00
mcndave 97f1f67af1 - Implemented CSRF check, some bugfixes also included 2022-02-03 20:06:04 +00:00
Dave Mc Nicoll 362184f81f - Quickfix on attributes 2021-10-19 12:39:49 +00:00
mcndave 0d9db0470e - Worked with the files begin on the context part 2021-03-09 15:27:54 +00:00
mcndave e75d997b02 - Added a new UiImage element.
- Inputs can now have a class array OR a simple string as 'class' param.
2021-03-01 16:35:57 +00:00
mcndave 34828f642c Small bugfixe within the UiPopup Widget 2021-01-06 20:24:54 +00:00
mcndave 6020fa300d Merge branch 'master' of https://git.mcnd.ca/mcndave/picea-ui 2020-10-06 15:54:35 +00:00
mcndave 7437bbfc73 - Casted type to string for selection matching 2020-10-06 15:50:27 +00:00
Dave Mc Nicoll 95c30be9f8 Merge branch 'master' of https://git.mcnd.ca/mcndave/picea-ui 2020-10-06 11:40:53 -04:00
Dave Mc Nicoll d05a750b39 - Fixed Numeric types (number) 2020-10-06 11:40:27 -04:00
Dave Mc Nicoll 512af58411 Merge branch 'master' of https://git.mcnd.ca/mcndave/picea-ui 2020-10-06 11:29:05 -04:00
Dave Mc Nicoll fd73d8e614 - Set formSent() public on FormHandler class
- Fixed class attributes on form which was overwritting it instead of adding to base class
2020-10-06 11:27:56 -04:00
58 changed files with 927 additions and 123 deletions

No files matched your search

+127
View File
@@ -0,0 +1,127 @@
# Picea UI
Welcome to the `picea-ui` documentation.
This quick documentation will guide you through the use of the form system, every available
UI elements and how to add customs one and individual components.
Mostly everything you need to build and process form is available in this package.
The first part is most input callable as a widget :
```
{% ui:text "fullname", "John Doe" %}
```
Current input support is :
```php
- Input (string $source, array $attributes = [], array $options = [])
- Checkbox (string $name, mixed $value = null, array $attributes = [], array $options = [])
- Color (string $name, mixed $value = null, array $attributes = [], array $options = [])
- Date (string $name, mixed $value = null, array $attributes = [], array $options = [])
- Datetime (string $name, mixed $value = null, array $attributes = [], array $options = [])
- Email (string $name, mixed $value = null, array $attributes = [], array $options = [])
- File (string $name, mixed $value = null, array $attributes = [], array $options = [])
- Hidden (string $name, mixed $value = null, array $attributes = [], array $options = [])
- Image (string $source, array $attributes = [], array $options = [])
- Numeric (string $name, mixed $value = null, array $attributes = [], array $options = [])
- Password (string $name, mixed $value = null, array $attributes = [], array $options = [])
- Radio (string $name, mixed $value = null, array $attributes = [], array $options = [])
- Range (string $name, mixed $value = null, array $attributes = [], array $options = [])
- Search (string $name, mixed $value = null, array $attributes = [], array $options = [])
- Select (string $name, array $list, mixed $value = null, array $attributes = [], bool $strictComparison = true)
- Tel (string $name, mixed $value = null, array $attributes = [], array $options = [])
- Text (string $name, mixed $value = null, array $attributes = [], array $options = [])
- Textarea (string $name, mixed $value = null, array $attributes = [], array $options = [])
- Time (string $name, mixed $value = null, array $attributes = [], array $options = [])
- Url (string $name, mixed $value = null, array $attributes = [], array $options = [])
- Week (string $name, mixed $value = null, array $attributes = [], array $options = [])
```
**[PICEA]** So, using this code:
```html
{% ui:form.post "user.save", current_url() %}
<div class="field fullname">
<label>Full name</label>
{% ui:text "fullname", $user->fullname, [ 'required' => 'required' ] %}
</div>
<div class="field email">
<label>Email address</label>
{% ui:text "email", $user->email %}
</div>
<div class="field job">
<label>Have a job</label>
<label>{% ui:radio "job", 'yes', [], [ 'value' => $entity->job ] %} Yes</label>
<label>{% ui:radio "job", 'no', [], [ 'value' => $entity->job ] %} No</label>
</div>
<div class="field role">
<label>Role</label>
{% ui:select "role", [ 'user' => "User", 'moderator' => "Moderator", 'admin' => "Admin" ], $user->role, [ 'required' => 'required' ] %}
</div>
<button type="submit">Save</button>
{% ui:endform %}
```
**[HTML]** Would render as such:
```html
<form class="ui-form" method="post" action="http://127.0.0.1/" enctype="multipart/form-data">
<input class="ui-hidden" type="hidden" name="picea-ui-form[user.save]" value="617fb40f1999db63fbd0b4f680a77750">
<div class="field fullname">
<label>Full name</label>
<input class="ui-text" type="text" value="" name="fullname" required="required">
</div>
<div class="field email">
<label>Email address</label>
<input class="ui-text" type="text" value="" name="email">
</div>
<div class="field job">
<label>Have a job</label>
<label><input class="ui-radio" type="radio" value="yes" name="job"> Yes</label>
<label><input class="ui-radio" type="radio" value="no" name="job"> No</label>
</div>
<div class="field role">
<label>Role</label>
<select class="ui-select" name="role" required="required">
<option value="user">User</option>
<option value="moderator">Moderator</option>
<option value="admin">Admin</option>
</select>
</div>
<button type="submit">Save</button>
</form>
```
## Use your forms in controller
Forms can be used to handle how your data is handled, from loading (get) to processing (post).
The process is as simple as defining a Form, implementing FormInterface :
```php
interface FormInterface
{
public function initialize(FormContextInterface $context) : void;
public function validate(FormContextInterface $context) : bool;
public function execute(FormContextInterface $context);
}
```
`initialize` is called directly on `form()` method call.
`validate` will be called if the request's method is either `post`, `put`, `patch` or `delete`, which implies **adding** / **editing** / **deleting**.
`execute` will finally launch only if `validate` returns `true`. This is usually where you want to **save** / **destroy** your entities.
+13
View File
@@ -0,0 +1,13 @@
# `ui:checkbox` (string $name, mixed $value = null, array $attributes = [], array $options = [])
Built on top of `ui:radio` component, which itself extends `ui:input`.
The following attributes are assigned by this extension :
- `'class' => "ui-checkbox"`
- `'type' => "checkbox"`
and will typically be rendered such as :
```html
<input class="ui-checkbox" type="checkbox" value="$value" name="$name">
```
+13
View File
@@ -0,0 +1,13 @@
# `ui:color` (string $name, mixed $value = null, array $attributes = [], array $options = [])
Built on top of `ui:input`.
The following attributes are assigned by this extension :
- `'class' => "ui-color"`
- `'type' => "color"`
and will typically be rendered such as :
```html
<input class="ui-color" type="color" value="$value" name="$name">
```
+13
View File
@@ -0,0 +1,13 @@
# `ui:date` (string $name, mixed $value = null, array $attributes = [], array $options = [])
Built on top of `ui:input`.
The following attributes are assigned by this extension :
- `'class' => "ui-date"`
- `'type' => "date"`
and will typically be rendered such as :
```html
<input class="ui-date" type="date" value="$value" name="$name">
```
+13
View File
@@ -0,0 +1,13 @@
# `ui:datetime` (string $name, mixed $value = null, array $attributes = [], array $options = [])
Built on top of `ui:input`.
The following attributes are assigned by this extension :
- `'class' => "ui-datetime"`
- `'type' => "datetime-local"`
and will typically be rendered such as :
```html
<input class="ui-datetime" type="datetime-local" value="$value" name="$name">
```
+13
View File
@@ -0,0 +1,13 @@
# `ui:email` (string $name, mixed $value = null, array $attributes = [], array $options = [])
Built on top of `ui:input`.
The following attributes are assigned by this extension :
- `'class' => "ui-email"`
- `'type' => "email"`
and will typically be rendered such as :
```html
<input class="ui-email" type="email" value="" name="">
```
+13
View File
@@ -0,0 +1,13 @@
# `ui:file` (string $name, mixed $value = null, array $attributes = [], array $options = [])
Built on top of `ui:input`.
The following attributes are assigned by this extension :
- `'class' => "ui-file"`
- `'type' => "file"`
and will typically be rendered such as :
```html
<input class="ui-file" type="file" value="" name="">
```
+18
View File
@@ -0,0 +1,18 @@
# `ui:form` / `ui:form.get` / `ui:form.post` (string $method = "get", string $name = "", string $action = "", array $attributes = [])
A version exists also for unsupported methods (as of HTML5) on forms `ui:form.put` / `ui:form.patch` / `ui:form.delete`, which
could find a use within a Javascript environment.
The following attributes are assigned by this extension :
- `'class' => "ui-form"`
- `'enctype' => "multipart/form-data"`
- `'method' => "get|post|put|patch|delete"`
and will typically be rendered such as :
```html
<form class="ui-form" enctype="multipart/form-data" action="$action" method="$method">
<input class="ui-hidden" type="hidden" name="picea-ui-form[$name]" value="{ random md5 hash }">
</form>
```
+13
View File
@@ -0,0 +1,13 @@
# `ui:hidden` (string $name, mixed $value = null, array $attributes = [], array $options = [])
Built on top of `ui:input`.
The following attributes are assigned by this extension :
- `'class' => "ui-hidden"`
- `'type' => "hidden"`
and will typically be rendered such as :
```html
<input class="ui-hidden" type="hidden" value="" name="">
```
+12
View File
@@ -0,0 +1,12 @@
# `ui:image` (string $source, array $attributes = [], array $options = [])
Built on top of `ui:input`.
The following attributes are assigned by this extension :
- `'class' => "ui-image"`
and will typically be rendered such as :
```html
<img class="ui-image" src="$source">
```
+13
View File
@@ -0,0 +1,13 @@
# `ui:input` (string $name, mixed $value = null, array $attributes = [], array $options = [])
This is small
The following attributes are assigned by this extension :
- `'class' => "ui-input"`
and will typically be rendered such as :
```html
<input class="ui-input" type="input" value="$value" name="$name">
```
+13
View File
@@ -0,0 +1,13 @@
# `ui:numeric` (string $name, mixed $value = null, array $attributes = [], array $options = [])
Built on top of `ui:input`.
The following attributes are assigned by this extension :
- `'class' => "ui-numeric"`
- `'type' => "numeric"`
and will typically be rendered such as :
```html
<input class="ui-numeric" type="numeric" value="$value" name="$name">
```
+13
View File
@@ -0,0 +1,13 @@
# `ui:password` (string $name, mixed $value = null, array $attributes = [], array $options = [])
Built on top of `ui:input`.
The following attributes are assigned by this extension :
- `'class' => "ui-password"`
- `'type' => "password"`
and will typically be rendered such as :
```html
<input class="ui-password" type="password" value="$value" name="$name">
```
+13
View File
@@ -0,0 +1,13 @@
# `ui:radio` (string $name, mixed $value = null, array $attributes = [], array $options = [])
Built on top of `ui:input`.
The following attributes are assigned by this extension :
- `'class' => "ui-radio"`
- `'type' => "radio"`
and will typically be rendered such as :
```html
<input class="ui-radio" type="radio" value="$value" name="$name">
```
+13
View File
@@ -0,0 +1,13 @@
# `ui:range` (string $name, mixed $value = null, array $attributes = [], array $options = [])
Built on top of `ui:input`.
The following attributes are assigned by this extension :
- `'class' => "ui-range"`
- `'type' => "range"`
and will typically be rendered such as :
```html
<input class="ui-range" type="range" value="$value" name="$name">
```
+13
View File
@@ -0,0 +1,13 @@
# `ui:search` (string $name, mixed $value = null, array $attributes = [], array $options = [])
Built on top of `ui:input`.
The following attributes are assigned by this extension :
- `'class' => "ui-search"`
- `'type' => "search"`
and will typically be rendered such as :
```html
<input class="ui-search" type="search" value="$value" name="$name">
```
+13
View File
@@ -0,0 +1,13 @@
# `ui:select` (string $name, array $list, $value = null, array $attributes = [], bool $strictComparison = true)
Built on top of `ui:input`.
The following attributes are assigned by this extension :
- `'class' => "ui-select"`
- `'type' => "select"`
and will typically be rendered such as :
```html
<input class="ui-select" type="select" value="$value" name="$name">
```
+13
View File
@@ -0,0 +1,13 @@
# `ui:tel` (string $name, mixed $value = null, array $attributes = [], array $options = [])
Built on top of `ui:input`.
The following attributes are assigned by this extension :
- `'class' => "ui-tel"`
- `'type' => "tel"`
and will typically be rendered such as :
```html
<input class="ui-tel" type="tel" value="$value" name="$name">
```
+13
View File
@@ -0,0 +1,13 @@
# `ui:text` (string $name, mixed $value = null, array $attributes = [], array $options = [])
Built on top of `ui:input`.
The following attributes are assigned by this extension :
- `'class' => "ui-text"`
- `'type' => "text"`
and will typically be rendered such as :
```html
<input class="ui-text" type="text" value="$value" name="$name">
```
+12
View File
@@ -0,0 +1,12 @@
# `ui:textarea` / `ui:textarea.raw` (string $name, mixed $value = null, array $attributes = [], array $options = [])
Built on top of `ui:input`. This extension also comes with a raw modifier which skips the escaping.
The following attributes are assigned by this extension :
- `'class' => "ui-textarea"`
and will typically be rendered such as :
```html
<textarea class="ui-textarea" name="$name">$value</textarea>
```
+13
View File
@@ -0,0 +1,13 @@
# `ui:time` (string $name, mixed $value = null, array $attributes = [], array $options = [])
Built on top of `ui:input`.
The following attributes are assigned by this extension :
- `'class' => "ui-time"`
- `'type' => "time"`
and will typically be rendered such as :
```html
<input class="ui-time" type="time" value="$value" name="$name">
```
+13
View File
@@ -0,0 +1,13 @@
# `ui:url` (string $name, mixed $value = null, array $attributes = [], array $options = [])
Built on top of `ui:input`.
The following attributes are assigned by this extension :
- `'class' => "ui-url"`
- `'type' => "url"`
and will typically be rendered such as :
```html
<input class="ui-url" type="url" value="$value" name="$name">
```
+13
View File
@@ -0,0 +1,13 @@
# `ui:week` (string $name, mixed $value = null, array $attributes = [], array $options = [])
Built on top of `ui:input`.
The following attributes are assigned by this extension :
- `'class' => "ui-week"`
- `'type' => "week"`
and will typically be rendered such as :
```html
<input class="ui-week" type="week" value="$value" name="$name">
```
+107
View File
@@ -0,0 +1,107 @@
# `form`(FormInterface $form, ? FormContext $formContext = null) : FormHandler
The `form()` method will process a form in three steps :
1. `initialize(FormContextInterface $context)` is first called on form processing.
2. `validate(FormContextInterface $context) : bool` will execute on POST. If you define a name to the form, it will match the one found in the POST (`picea-ui-form[$context->name]`) ; if the form is not the target, it will be skipped. Also, if an error si found within the message list, the method will return `false`.
3. `execute(FormContextInterface $context)` is the last method to run, which is usually where you save your entity and finilize your messages. The return value is accessible from the `formHandler` which `form()` returns.
**[PHP]** So, using this code:
in */app/controller/user.php*
```php
form(new \MyApp\Form\User\Create());
```
*/app/form/create.php*
```php
<?php
namespace MyProject\Form;
use MyProject\{ Lib, Entity };
use Picea\Ui\Method\{ FormContextInterface, Message\ErrorMessage };
use Psr\Http\Message\ServerRequestInterface;
use function MyProject\View\{ lang };
class Create implements \Picea\Ui\Method\FormInterface {
public function initialize(FormContextInterface $context) : void
{
$this->user = new Entity\User();
# $context->{varname} will be populated by $_POST data.
$this->user = (string) $context->fullname;
}
public function validate(FormContextInterface $context) : bool
{
if ( strlen($this->user) < 3 ) {
# This won't allow `execute` to run because of the error message.
$context->pushMessage(Lib\Message::generateError(
lang("form.error.provide_fullName")
));
}
return $context->valid();
}
public function execute(FormContextInterface $context) : void
{
# Saving a new user here
Entity\User::repository()->save($this->user);
$context->pushMessage(Lib\Message::generateSuccess(
lang("form.save")
));
}
}
```
which implements this interface :
```php
interface FormInterface
{
public function initialize(FormContextInterface $context) : void;
public function validate(FormContextInterface $context) : bool;
public function execute(FormContextInterface $context);
}
```
Would yield the same result such as:
```php
true
true
```
Trying to access either variable before `execute` is called will return `null`.
## `FormContext` object
`__construct(ServerRequestInterface $request, ? string $formName = null)`
Creating a form context manually allows to define the formName, which is essential if you want to use the
Cross-Scripting Request Forgery (CSRF).
It is populated with either $_POST or request's body which is json_decode. A property called `files` is also populated from PSR's `$request->getUploadedFiles()`.
It's `valid() : bool` method called into a form `validate()` function is first checking every message which were pushed into, using `pushMessage(FormMessage $message)`, and returns false if an error message is found in the list.
### Retrieve a form execution status / value
There is two ways to get the execution status (from the `execute` return value):
```php
$formContext = new \Picea\Ui\Method\FormContext($request, 'form.user_create');
$form = form(new \MyProject\Form\User\Create(), $formContext);
if ($formContext->formExecuted) {
echo $form->executionStatus;
echo $formContext->formExecutionStatus;
}
```
+8
View File
@@ -0,0 +1,8 @@
# `ui:popup` (string $name, array $variables = [], array $options = [])
The following attributes are assigned by this extension :
and will typically be rendered such as (directly inside an opening tag) :
```html
ui-popup='{ "name":$name,"vars":{$variables},"options":{$options} }'
```
+29 -15
View File
@@ -26,9 +26,9 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
* tag-type single Only render a single tag. Cannot contains any childrens
* force-tag-open tag Control the way the opening tag is rendered
* force-tag-close tag-close " " " " closing " " "
* no-attr Attributes will not be rendered
* no-id ID is not automatically given (* may be removed *)
* escape-tag-end If you need to echo the Node inside another string, it may be useful to escape the "/" char using "\/"
* no-attr true Attributes will not be rendered
* no-id true ID is not automatically given (* may be removed *)
* escape-tag-end true If you need to echo the Node inside another string, it may be useful to escape the "/" char using "\/"
*/
public array $options = [];
@@ -148,7 +148,7 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
return $this;
}
public function option(string $var, $value = 1) : self
public function option(string $var, mixed $value = true) : self
{
$this->options[$var] = $value;
@@ -177,8 +177,10 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
}
}
else if ( !is_numeric($key) ) {
# will output something like <tag $key=$value></tag>
$attributesList[] = strpos($value, '"') !== false ? "$key='$value'" : "$key=\"$value\"";
$value = htmlspecialchars($value);
# will output something like <tag $key="$value"></tag>
$attributesList[] = "$key=\"$value\"";
}
else {
# will output something like <tag $value></tag>
@@ -198,14 +200,14 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
}
}
if ( in_array('no-tag', $this->options, true) ) {
if ( $this->options['no-tag'] ?? false ) {
return $this->content . $content;
}
else {
$compiledAttributes = (count($attributesList) && ! in_array('no-attr', $this->options, true) ? " " . implode(" ", $attributesList) : "");
$compiledAttributes = $this->options['no-attr'] ?? false ? "" : " " . implode(" ", $attributesList);
# Force the node to contain a certain opening tag (php is a good example)
if ( in_array('force-tag-open', $this->options, true) ) {
if ( $this->options['force-tag-open'] ?? false ) {
$opentag = $this->options['force-tag-open'] . $compiledAttributes;
}
else {
@@ -217,11 +219,11 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
$closetag = "";
}
}
elseif ( in_array('force-tag-close', $this->options, true) ) {
elseif ( $this->options['force-tag-close'] ?? false ) {
$closetag = $this->options['force-tag-close'];
}
else {
$closetag = $this->tag ? "<" . (in_array('escape-tag-end', $this->options) ? "\/" : "/" ) . "{$this->tag}>" : "";
$closetag = $this->tag ? "<" . ( $this->options['escape-tag-end'] ?? false ? "\/" : "/" ) . "{$this->tag}>" : "";
}
return $opentag . $this->content . $content . $closetag;
@@ -240,7 +242,7 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
public function text(?string $set = null) {
if ($set !== null) {
$this->content = htmlspecialchars( $set, ENT_NOQUOTES );
$this->content = htmlspecialchars( $set, \ENT_NOQUOTES );
return $this;
}
@@ -258,7 +260,6 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
return $this->insert( static::INSERT_MODE_PREPEND, ...( is_array($arguments) ? array_reverse($arguments) : $arguments) );
}
protected function insert(int $mode, ...$elements) : self
{
foreach($elements as $item) {
@@ -285,14 +286,24 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
break;
case 'style' :
if ( is_string($value) )
{
foreach(array_filter(explode(";", $value)) as $vars) {
list($key, $value) = explode(':', $vars);
$this->css($key, $value);
}
}
elseif ( is_array($value) ) {
foreach($value as $var => $val) {
$this->css($var, $val);
}
}
break;
default:
if ( is_numeric($key) ) {
$this->attributes[] = $value;
$this->attributes[$key] = $value;
}
elseif ( is_array($this->attributes[$key] ?? false) ) {
$this->attributes[$key] = array_replace($value, $this->attributes[$key]);
@@ -343,7 +354,8 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
return count($this->childs);
}
public function jsonSerialize() {
public function jsonSerialize() : mixed
{
return [
'tag' => $this->tag,
'attr' => $this->attributes,
@@ -352,6 +364,7 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
];
}
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value) {
if ( is_numeric($offset) ) {
return $this->selected[$offset] = $value;
@@ -394,6 +407,7 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
return key($this->childs);
}
#[\ReturnTypeWillChange]
public function next() : self
{
return next($this->childs);
+2 -2
View File
@@ -9,7 +9,7 @@ use Picea\Extension\ExtensionTrait;
class UiMessage extends UiElement implements Extension {
use ExtensionTrait;
public string $token = "ui.message";
public string $token = "ui:message";
public string $tag = "input";
@@ -17,7 +17,7 @@ class UiMessage extends UiElement implements Extension {
'class' => 'ui-message',
];
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : string
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token) : string
{
return "";
}
+4 -4
View File
@@ -9,7 +9,7 @@ use Picea\Extension\ExtensionTrait;
class UiPopup extends UiElement implements Extension {
use ExtensionTrait;
public string $token = "ui.popup";
public string $token = "ui:popup";
public string $tag = "div";
@@ -17,14 +17,14 @@ class UiPopup extends UiElement implements Extension {
'class' => 'ui-popup',
];
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : string
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token) : string
{
return "<?php echo 'ui-popup=\"' . ( new \\" . static::class . "() )->buildAttributes($arguments) . '\"' ?>";
}
public function buildAttributes(string $name, array $variables = []) : string
public function buildAttributes(string $name, array $variables = [], array $options = []) : string
{
return htmlentities(json_encode([ "name" => $name, "vars" => $variables ], JSON_HEX_APOS | JSON_HEX_QUOT), ENT_QUOTES, 'UTF-8');
return htmlentities(json_encode([ "name" => $name, "vars" => $variables, "options" => $options ], JSON_HEX_APOS | JSON_HEX_QUOT), \ENT_QUOTES, 'UTF-8');
}
}
+129
View File
@@ -0,0 +1,129 @@
<?php
namespace Picea\Ui\Form;
use Picea\Extension\Extension;
use Picea\Extension\ExtensionTrait;
use Picea\Ui\Common\UiElement;
use Picea\Ui\Component\UiMessage;
use Picea\Ui\Component\UiPopup;
class Ui extends UiElement implements Extension {
use ExtensionTrait;
public string $token = "ui";
protected /* ? mixed */ $value;
protected string $name;
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token) : string
{
$opt = $context->tokenOptions($token);
switch(true) {
case in_array('checkbox', $opt):
$input = new UiCheckbox();
break;
case in_array('color', $opt):
$input = new UiColor();
break;
case in_array('date', $opt):
$input = new UiDate();
break;
case in_array('datetime', $opt):
$input = new UiDatetime();
break;
case in_array('email', $opt):
$input = new UiEmail();
break;
case in_array('file', $opt):
$input = new UiFile();
break;
case in_array('form', $opt):
case in_array('endform', $opt):
$input = new UiForm();
break;
case in_array('hidden', $opt):
$input = new UiHidden();
break;
case in_array('image', $opt):
$input = new UiImage();
break;
case in_array('input', $opt):
$input = new UiInput();
break;
case in_array('numeric', $opt):
$input = new UiNumeric();
break;
case in_array('password', $opt):
$input = new UiPassword();
break;
case in_array('radio', $opt):
$input = new UiRadio();
break;
case in_array('range', $opt):
$input = new UiRange();
break;
case in_array('search', $opt):
$input = new UiSearch();
break;
case in_array('select', $opt):
$input = new UiSelect();
break;
case in_array('tel', $opt):
$input = new UiTel();
break;
case in_array('text', $opt):
$input = new UiText();
break;
case in_array('textarea', $opt):
$input = new UiTextarea();
break;
case in_array('time', $opt):
$input = new UiText();
break;
case in_array('url', $opt):
$input = new UiUrl();
break;
case in_array('week', $opt):
$input = new UiWeek();
break;
case in_array('popup', $opt):
$input = new UiPopup();
break;
case in_array('message', $opt):
$input = new UiMessage();
break;
}
if (empty($input)) {
throw new \Exception("Missing token ? $token");
}
return $input->parse($context, $arguments, $token);
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
class UiCheckbox extends UiRadio {
public string $token = "ui.checkbox";
public string $token = "ui:checkbox";
public array $attributes = [
'class' => "ui-checkbox",
+1 -1
View File
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
class UiColor extends UiInput {
public string $token = "ui.color";
public string $token = "ui:color";
public array $attributes = [
'class' => "ui-color",
+1 -1
View File
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
class UiDate extends UiInput {
public string $token = "ui.date";
public string $token = "ui:date";
public array $attributes = [
'class' => "ui-date",
+1 -1
View File
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
class UiDatetime extends UiInput {
public string $token = "ui.datetime";
public string $token = "ui:datetime";
public array $attributes = [
'class' => "ui-datetime",
+1 -1
View File
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
class UiEmail extends UiInput {
public string $token = "ui.email";
public string $token = "ui:email";
public array $attributes = [
'class' => "ui-email",
+1 -1
View File
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
class UiFile extends UiInput {
public string $token = "ui.file";
public string $token = "ui:file";
public array $attributes = [
'class' => "ui-file",
+44 -22
View File
@@ -11,7 +11,7 @@ class UiForm extends UiElement implements Extension {
public string $defaultMethod = "get";
public array $token = [ "ui.form", "ui.endform", "ui.form.get", "ui.form.post", "ui.form.patch", "ui.form.delete", "ui.form.put" ];
public array $token = [ "ui:form", "ui:endform" /*, "ui:form.get", "ui:form.post", "ui:form.patch", "ui:form.delete", "ui:form.put"*/ ];
public string $tag = "form";
@@ -19,36 +19,41 @@ class UiForm extends UiElement implements Extension {
'class' => 'ui-form',
];
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : string
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token) : string
{
switch($token) {
case 'ui.endform':
case 'ui:endform':
return "</form>";
}
case "ui.form.get":
$opt = $context->tokenOptions($token, true);
if (in_array('get', $opt)) {
$method = "get";
break;
case "ui.form.patch":
$method = "patch";
break;
case "ui.form.delete":
$method = "delete";
break;
case "ui.form.put":
$method = "put";
break;
case "ui.form.post":
}
elseif (in_array('post', $opt)) {
$method = "post";
break;
}
elseif (in_array('put', $opt)) {
$method = "put";
}
elseif (in_array('delete', $opt)) {
$method = "delete";
}
elseif (in_array('patch', $opt)) {
$method = "patch";
}
$method ??= $this->defaultMethod;
return "<?php echo ( new \\" . static::class . "() )->buildHtml('$method', $arguments) ?>";
return "<?php echo ( new \\" . static::class . "() )->parseOptions($opt)->buildHtml('$method', $arguments) ?>";
}
public function parseOptions($options) : self
{
$this->options = $options;
return $this;
}
public function buildHtml(string $method = "get", string $name = "", string $action = "", array $attributes = []) : string
@@ -57,10 +62,27 @@ class UiForm extends UiElement implements Extension {
$this->option('tag-type', 'single');
if ($attributes['class'] ?? false) {
$attributes['class'] .= " {$this->attributes['class']}";
unset($this->attributes['class']);
}
$this->attributes([ 'method' => $method, 'action' => $action ] + $attributes);
if ( $method !== "get" ) {
$this->append( ( new UiHidden() )->attributes([ 'name' =>"picea-ui-form[$name]", 'value' => md5( $name . microtime() ) ]) );
$token = md5( $name . microtime());
$key = "picea-ui:form:{$name}";
if (count($_SESSION[$key] ?? []) > 100) {
array_shift($_SESSION[$key]);
}
$_SESSION[$key][] = $token;
$this->append( ( new UiHidden() )->attributes([
'name' => "picea-ui-form[$name]",
'value' => $token,
]));
$this->attributes([ 'enctype' => "multipart/form-data" ]);
}
+1 -1
View File
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
class UiHidden extends UiInput {
public string $token = "ui.hidden";
public string $token = "ui:hidden";
public array $attributes = [
'class' => "ui-hidden",
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace Picea\Ui\Form;
use Picea\Ui\Common\UiElement;
use Picea\Extension\Extension;
use Picea\Extension\ExtensionTrait;
class UiImage extends UiElement implements Extension {
use ExtensionTrait;
public array $tokens = [ "ui:img", "ui:image" ];
public string $tag = "img";
public array $attributes = [
'class' => 'ui-image',
];
public array $options = [
'tag-type' => "single",
];
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token) : string
{
return "<?php echo ( new \\" . static::class . "() )->buildHtml($arguments) ?>";
}
public function buildHtml(string $source, array $attributes = [], array $options = []) : string
{
if ($options) {
$this->options += $options;
}
if ($attributes['class'] ?? false) {
$attributes['class'] .= " {$this->attributes['class']}";
unset($this->attributes['class']);
}
$this->attributes([ 'src' => $source ] + $attributes + $this->attributes + $this->objectAttribute());
return $this->render();
}
protected function objectAttribute() : array
{
return [];
}
}
+4 -4
View File
@@ -9,7 +9,7 @@ use Picea\Extension\ExtensionTrait;
class UiInput extends UiElement implements Extension {
use ExtensionTrait;
public string $token = "ui.input";
public string $token = "ui:input";
public string $tag = "input";
@@ -25,12 +25,12 @@ class UiInput extends UiElement implements Extension {
protected string $name;
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : string
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token) : string
{
return "<?php echo ( new \\" . static::class . "() )->buildHtml($arguments) ?>";
}
public function buildHtml(string $name, /*? mixed */ $value = null, array $attributes = [], array $options = []) : string
public function buildHtml(string $name, mixed $value = null, array $attributes = [], array $options = []) : string
{
$this->name = $name;
@@ -43,7 +43,7 @@ class UiInput extends UiElement implements Extension {
}
if ($attributes['class'] ?? false) {
$attributes['class'] .= " {$this->attributes['class']}";
$attributes['class'] = implode(" ", array_merge((array) $attributes['class'], (array) $this->attributes['class']));
unset($this->attributes['class']);
}
+2 -2
View File
@@ -4,10 +4,10 @@ namespace Picea\Ui\Form;
class UiNumeric extends UiInput {
public string $token = "ui.numeric";
public string $token = "ui:numeric";
public array $attributes = [
'class' => "ui-numeric",
'type' => "numeric",
'type' => "number",
];
}
+1 -1
View File
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
class UiPassword extends UiInput {
public string $token = "ui.password";
public string $token = "ui:password";
public array $attributes = [
'class' => "ui-password",
+1 -1
View File
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
class UiRadio extends UiInput {
public string $token = "ui.radio";
public string $token = "ui:radio";
public array $attributes = [
'class' => "ui-radio",
+1 -1
View File
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
class UiRange extends UiInput {
public string $token = "ui.range";
public string $token = "ui:range";
public array $attributes = [
'class' => "ui-range",
+1 -1
View File
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
class UiSearch extends UiInput {
public string $token = "ui.search";
public string $token = "ui:search";
public array $attributes = [
'class' => "ui-search",
+15 -6
View File
@@ -9,7 +9,7 @@ use Picea\Extension\ExtensionTrait;
class UiSelect extends UiElement implements Extension {
use ExtensionTrait;
public string $token = "ui.select";
public string $token = "ui:select";
public string $tag = "select";
@@ -17,13 +17,18 @@ class UiSelect extends UiElement implements Extension {
'class' => 'ui-select',
];
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : string
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token) : string
{
return "<?php echo ( new \\" . static::class . "() )->buildHtml($arguments) ?>";
}
public function buildHtml(string $name, array $list, $value = null, array $attributes = [], bool $strictComparison = true) : string
public function buildHtml(string $name, array $list, mixed $value = null, array $attributes = [], bool $strictComparison = true) : string
{
if ($attributes['class'] ?? false) {
$attributes['class'] = implode(" ", array_merge((array) $attributes['class'], (array) $this->attributes['class']));
unset($this->attributes['class']);
}
$this->attributes([ 'name' => $name ] + $attributes + $this->attributes);
$this->setList($list, $value, $strictComparison);
@@ -38,7 +43,6 @@ class UiSelect extends UiElement implements Extension {
protected function setList(array $list, $selected, bool $strict = true) : void
{
foreach($list as $key => $item) {
if ($item instanceof UiElement) {
$this->append($item);
@@ -49,11 +53,11 @@ class UiSelect extends UiElement implements Extension {
$obj->attributes(['label' => $key]);
foreach($item as $subKey => $subItem) {
$obj->append($this->createOption($subItem, $subKey, $this->isSelected($subKey, $selected, $strict)));
$obj->append($this->createOption((string) $subItem, $subKey, $this->isSelected($subKey, $selected, $strict)));
}
}
else {
$obj = $this->createOption($item, $key, $this->isSelected($key, $selected, $strict));
$obj = $this->createOption((string) $item, $key, $this->isSelected($key, $selected, $strict));
}
$this->append($obj);
@@ -75,6 +79,11 @@ class UiSelect extends UiElement implements Extension {
protected function isSelected($check, $value, bool $strict = true) : bool
{
if ( is_array($value) ) {
return in_array($check, $value, $strict);
}
return $strict ? $check === $value : $check == $value;
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
class UiTel extends UiInput {
public string $token = "ui.tel";
public string $token = "ui:tel";
public array $attributes = [
'class' => "ui-tel",
+1 -1
View File
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
class UiText extends UiInput {
public string $token = "ui.text";
public string $token = "ui:text";
public array $attributes = [
'class' => "ui-text",
+18 -2
View File
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
class UiTextarea extends UiInput {
public string $token = "ui.textarea";
public array $tokens = [ "ui:textarea" ];
public string $tag = "textarea";
@@ -14,8 +14,24 @@ class UiTextarea extends UiInput {
public array $options = [];
protected bool $echoRaw = false;
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token) : string
{
$raw = in_array('raw', $context->tokenOptions($token, true)) ? 'true' : 'false';
return "<?php echo ( new \\" . static::class . "() )->echoRaw($raw)->buildHtml($arguments) ?>";
}
protected function setValue($value) : void
{
$this->html($value);
$this->echoRaw ? $this->html($value) : $this->text($value);
}
public function echoRaw(bool $set) : self
{
$this->echoRaw = $set;
return $this;
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
class UiTime extends UiInput {
public string $token = "ui.time";
public string $token = "ui:time";
public array $attributes = [
'class' => "ui-time",
+1 -1
View File
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
class UiUrl extends UiInput {
public string $token = "ui.url";
public string $token = "ui:url";
public array $attributes = [
'class' => "ui-url",
+1 -1
View File
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
class UiWeek extends UiInput {
public string $token = "ui.week";
public string $token = "ui:week";
public array $attributes = [
'class' => "ui-week",
+8 -22
View File
@@ -7,9 +7,10 @@ use Picea\Extension\Extension,
use Picea\Compiler\Context;
use Picea\Extension\FunctionExtension;
use Psr\Http\Message\ServerRequestInterface;
class Form implements Extension {
class Form implements Extension, FunctionExtension {
use ExtensionTrait;
public array $tokens;
@@ -22,34 +23,19 @@ class Form implements Extension {
public function __construct(ServerRequestInterface $request, Context $context) {
$this->request = $request;
$this->register($context);
}
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : string { }
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token) : string { }
public function register(Context $context) : void
public function exportFunctions(): array
{
$context->pushFunction("form", [ $this, 'formClass' ]);
}
public function form_csrf(string $field, string $value) {
$values = $this->session("View.form.csrf.$field") ?: [];
# keeps 20 (from config) latest CSRF key for this form into session,
# allowing more than one tab opened and preventing information loss
if ( count($values) >= 20 ) {
#array_shift($values);
}
$values[] = $value;
$this->session("View.form.csrf.$field", $values);
return $value;
return [
"form" => [ $this, 'formClass' ],
];
}
public function formClass(FormInterface $form, ? FormContext $formContext = null) : FormHandler
{
return new FormHandler($this->request, $form, $formContext);
return new FormHandler($formContext ? $formContext->request : $this->request, $form, $formContext);
}
}
+43 -2
View File
@@ -9,12 +9,20 @@ class FormContext implements FormContextInterface
{
public string $formName;
public bool $formSent;
public bool $formSent = false;
public bool $formExecuted = false;
public mixed $formExecutionStatus = null;
public array $values = [];
public array $files = [];
public array $messages = [];
public bool $skipCsrf = false;
public ServerRequestInterface $request;
public ? ResponseInterface $response = null;
@@ -28,6 +36,16 @@ class FormContext implements FormContextInterface
}
$this->values = $request->getParsedBody() ?: [];
if ( ! $this->values ) {
$content = utf8_encode((string) $request->getBody());
if ( $content && ( $json = json_decode($content, true) ) ) {
$this->values = $json;
}
}
$this->files = $request->getUploadedFiles() ?: [];
}
public function valid() : bool
@@ -41,9 +59,32 @@ class FormContext implements FormContextInterface
return true;
}
public function executed() : bool
{
return $this->formExecuted;
}
public function formSent() : bool
{
return $this->formSent;
$valid = in_array($this->request->getMethod(), [ 'POST', 'PUT', 'PATCH', 'DELETE', ]);
if ( ! $this->skipCsrf && ($this->formName ?? false) ) {
$token = $this->get('picea-ui-form')[$this->formName] ?? false;
if ( $token ) {
if ($this->validateCsrfToken) {
$valid = in_array($token, $_SESSION["picea-ui.form:{$this->formName}"] ?? []);
}
else {
$valid = (bool) $token;
}
}
else {
$valid = false;
}
}
return $this->formSent = $valid;
}
public function __set($key, $value)
+18 -19
View File
@@ -5,15 +5,25 @@ namespace Picea\Ui\Method;
use Psr\Http\Message\ServerRequestInterface;
class FormHandler {
const DEFAULT_METHODS = [
"DELETE", "PATCH", "POST", "PUT",
];
public bool $sent = false;
public ? bool $executionStatus = null;
public bool $validateCsrfToken = true;
public /* mixed */ $executionStatus = null;
public ? bool $validationStatus = null;
public FormInterface $form;
public FormContext $context;
protected ServerRequestInterface $request;
protected FormInterface $form;
public array $acceptedMethods = self::DEFAULT_METHODS;
public function __construct(ServerRequestInterface $request, FormInterface $form, ? FormContextInterface $context = null)
{
@@ -28,37 +38,26 @@ class FormHandler {
$this->context = method_exists($form, 'getContext') ? $form->getContext($request) : new FormContext($request);
}
$this->request->context = $this->context;
$this->request->withAttribute('picea.context', $this->context);
$this->formSent();
$this->initialize();
}
protected function formSent() : void
{
if ( false !== $this->context->formSent = $this->sent ) {
if ( $this->context->formName ?? false ) {
$this->sent = $this->context->formSent = (bool) ( $this->request->getParsedBody()['picea-ui-form'][$this->context->formName] ?? false );
}
}
}
protected function initialize() : void
{
$this->form->initialize($this->context);
if ( $this->sent ) {
if ( $this->form->validate($this->context) ) {
$this->executionStatus = $this->form->execute($this->context);
if ( $this->sent && $this->context->formSent() ) {
if ( $this->validationStatus = $this->form->validate($this->context) ) {
$this->context->formExecutionStatus = $this->executionStatus = $this->form->execute($this->context);
$this->context->formExecuted = true;
}
}
}
protected function requestSent() : bool
{
return in_array($this->request->getMethod(), [
"DELETE", "PATCH", "POST", "PUT",
]);
return in_array(strtoupper($this->request->getMethod()), array_map('strtoupper', $this->acceptedMethods));
}
protected function honeyPot() : bool
+1 -1
View File
@@ -8,5 +8,5 @@ interface FormInterface
public function validate(FormContextInterface $context) : bool;
public function execute(FormContextInterface $context) : void;
public function execute(FormContextInterface $context);
}
+8 -5
View File
@@ -7,9 +7,10 @@ use Picea\Extension\Extension,
use Picea\Compiler\Context;
use Picea\Extension\FunctionExtension;
use Psr\Http\Message\ServerRequestInterface;
class Pagination implements Extension {
class Pagination implements Extension, FunctionExtension {
use ExtensionTrait;
public array $tokens;
@@ -20,14 +21,16 @@ class Pagination implements Extension {
public function __construct(ServerRequestInterface $request, Context $context) {
$this->request = $request;
$this->register($context);
}
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : string { }
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token) : string { }
public function register(Context $context) : void
public function exportFunctions(): array
{
$context->pushFunction("pagination", [ $this, 'paginationClass' ]);
return [
"pagination" => [ $this, 'paginationClass' ]
];
}
public function paginationClass(string $name, ?string $url = null, ?array $params = null) : PaginationHandler
+1 -1
View File
@@ -41,7 +41,7 @@ class PaginationHandler {
}
public function __toString() {
return "hello world";
return "";
}
}
+4
View File
@@ -8,6 +8,9 @@ class Ui {
public function registerFormExtension(Compiler $compiler) : self
{
# BW compatibility with v1.x syntax
$compiler->registerExtension(new Form\Ui());
$compiler->registerExtension(new Form\UiForm());
$compiler->registerExtension(new Form\UiInput());
$compiler->registerExtension(new Form\UiCheckbox());
@@ -29,6 +32,7 @@ class Ui {
$compiler->registerExtension(new Form\UiUrl());
$compiler->registerExtension(new Form\UiWeek());
$compiler->registerExtension(new Form\UiSelect());
$compiler->registerExtension(new Form\UiImage());
$compiler->registerExtension(new Component\UiPopup());