1047 lines
33 KiB
PHP
1047 lines
33 KiB
PHP
|
<?php
|
||
|
namespace nur\v\bs3\fo;
|
||
|
|
||
|
use ArrayAccess;
|
||
|
use Countable;
|
||
|
use nur\A;
|
||
|
use nur\b\coll\TArrayTools;
|
||
|
use nur\b\coll\TBaseArray;
|
||
|
use nur\b\coll\TGenericArray;
|
||
|
use nur\b\params\IParametrable;
|
||
|
use nur\b\params\Tparametrable1;
|
||
|
use nur\data\types\IType;
|
||
|
use nur\data\types\md_utils;
|
||
|
use nur\data\types\Metadata;
|
||
|
use nur\F;
|
||
|
use nur\msg;
|
||
|
use nur\v\al;
|
||
|
use nur\v\base\Alerter;
|
||
|
use nur\v\base\ComponentPrintable;
|
||
|
use nur\v\v;
|
||
|
use nur\v\vo;
|
||
|
|
||
|
class Form extends ComponentPrintable implements IParametrable, ArrayAccess, Countable {
|
||
|
use Tparametrable1, TBaseArray, TGenericArray, TArrayTools;
|
||
|
|
||
|
/** @return bool est-ce un formulaire de type "horizontal" par opposition à "inline" */
|
||
|
function HORIZONTAL(): bool {
|
||
|
return static::HORIZONTAL;
|
||
|
} const HORIZONTAL = false;
|
||
|
/** @return ?string classe pour les boutons submit en form horizontal */
|
||
|
function FGS_CLASS(): ?string { return null; }
|
||
|
/** @return ?string classe pour les labels en form horizontal */
|
||
|
function FGL_CLASS(): ?string { return null; }
|
||
|
/** @return ?string classe pour les contrôle en form horizontal */
|
||
|
function FGC_CLASS(): ?string { return null; }
|
||
|
function SHOW_HELP(): bool {
|
||
|
return static::SHOW_HELP;
|
||
|
} const SHOW_HELP = true;
|
||
|
|
||
|
/** @var ?string|?array classes spécifiques à ce formulaire */
|
||
|
const FORM_CLASS = null;
|
||
|
|
||
|
/** @var bool faut-il tenir compte du paramètre [autohide_label]? */
|
||
|
const ALLOW_AUTOHIDE_LABEL = false;
|
||
|
|
||
|
/** @var ?array schéma des données du formulaire */
|
||
|
const SCHEMA = null;
|
||
|
|
||
|
/** @var array[] schéma de la définition des paramètres de formulaire */
|
||
|
const PARAMS_SCHEMA = [
|
||
|
"key" => ["string", null, "nom de la clé dans le schéma"],
|
||
|
"name" => ["?string", null, "nom du paramètre"],
|
||
|
"default" => [null, null, "valeur par défaut du paramètre"],
|
||
|
"value" => [null, null, "valeur du paramètre à forcer"],
|
||
|
"label" => [null, null, "label du contrôle de formulaire"],
|
||
|
"control" => ["?string", null, "classe de contrôle de formulaire à utiliser"],
|
||
|
];
|
||
|
|
||
|
/** @var ?array paramètres de formulaire à récupérer */
|
||
|
const PARAMS = null;
|
||
|
|
||
|
/** @var ?array paramètres à exclure de la liste calculée automatiquement */
|
||
|
const PARAMS_EXCLUDES = null;
|
||
|
|
||
|
/**
|
||
|
* @var ?array paramètres de formulaire à récupérer. cette valeur est mergée
|
||
|
* avec PARAMS. cela permet d'augmenter le paramétrage calculé automatiquement
|
||
|
*/
|
||
|
const PARAMS_OVERRIDES = null;
|
||
|
|
||
|
const INVALID_MSG = null;
|
||
|
|
||
|
/** @var ?array paramètres du bouton submit automatique */
|
||
|
const SUBMIT = "Envoyer";
|
||
|
|
||
|
const CONTROL_PREFIX = "\n";
|
||
|
|
||
|
const CONTROL_SUFFIX = null;
|
||
|
|
||
|
const SUBMITTED_KEY = null;
|
||
|
|
||
|
const CONTROL_CLASSES = [
|
||
|
"hidden" => ControlHidden::class,
|
||
|
"fixed" => ControlFixed::class,
|
||
|
"text" => ControlText::class,
|
||
|
"password" => ControlPassword::class,
|
||
|
"select" => ControlSelect::class,
|
||
|
"checkbox" => ControlCheckbox::class,
|
||
|
"radiobutton" => ControlRadiobutton::class,
|
||
|
"textarea" => ControlTextarea::class,
|
||
|
"file" => ControlFile::class,
|
||
|
"submit" => ControlSubmit::class,
|
||
|
"reset" => ControlReset::class,
|
||
|
];
|
||
|
|
||
|
/** @var int type de contrôle: name, name+value ou name+value+checked */
|
||
|
const N = 1, NV = 2, NVC = 3;
|
||
|
const CONTROL_TYPES = [
|
||
|
ControlHidden::class => [self::NV, null],
|
||
|
ControlFixed::class => [self::NV, "label"],
|
||
|
ControlText::class => [self::NV, "label"],
|
||
|
ControlPassword::class => [self::NV, "label"],
|
||
|
ControlSelect::class => [self::NV, "label"],
|
||
|
ControlCheckbox::class => [self::NVC, "text"],
|
||
|
ControlRadiobutton::class => [self::NVC, "text"],
|
||
|
ControlTextarea::class => [self::NV, "label"],
|
||
|
ControlFile::class => [self::N, "label"],
|
||
|
ControlSubmit::class => [self::NV, "submit"],
|
||
|
ControlReset::class => [self::NV, "reset"],
|
||
|
];
|
||
|
|
||
|
const PARAMETRABLE_PARAMS_SCHEMA = [
|
||
|
"action" => ["?string", null, "url destination du formulaire"],
|
||
|
"method" => ["?string", "get", "méthode du formulaire (post, get)"],
|
||
|
"enctype" => ["?string", null, "type d'encodage du formulaire"],
|
||
|
"upload" => ["bool", null, "ce formulaire est-il utilisé pour uploader des fichiers?"],
|
||
|
"class" => ["?array", null, "classes CSS du formulaire"],
|
||
|
"attrs" => ["?array", null, "attributs HTML supplémentaires"],
|
||
|
"autohide_label" => ["bool", true, "faut-il cacher le label s'il y a le placeholder?"],
|
||
|
"schema" => ["?array", null, "schéma des données du formulaire"],
|
||
|
"params" => ["?array", null, "paramètres de formulaire à récupérer"],
|
||
|
"params_excludes" => ["?array", null, "paramètres à exclure de la liste calculée automatiquement"],
|
||
|
"params_overrides" => ["?array", null, "paramètres de formulaire à récupérer à merger avec [params]"],
|
||
|
"alerter" => [Alerter::class, null, "instance utilisée pour afficher un message avant le bouton submit"],
|
||
|
"invalid_msg" => ["?content", null, "message à afficher si le formulaire est invalide"],
|
||
|
"submit" => ["?array", null, "paramètres du bouton submit automatique"],
|
||
|
"submits" => ["?array", null, "boutons submit supplémentaires"],
|
||
|
"autoadd_submit" => ["bool", true, "Faut-il rajouter le bouton submit automatique?"],
|
||
|
"control_prefix" => [null, null, "valeur à afficher avant chaque contrôle"],
|
||
|
"control_suffix" => [null, null, "valeur à afficher après chaque contrôle"],
|
||
|
"submitted_key" => ["?string", null, "nom d'un champ du formulaire qui, s'il existe, indique que le formulaire a été soumis"],
|
||
|
"autoload_params" => ["bool", false, "faut-il charger les paramètres de formulaire de suite?"],
|
||
|
"autoprint" => ["bool", false, "faut-il afficher le formulaire de suite?"],
|
||
|
];
|
||
|
|
||
|
function __construct(?array $params=null) {
|
||
|
self::set_parametrable_params_defaults($params, [
|
||
|
"schema" => static::SCHEMA,
|
||
|
"params_exclude" => static::PARAMS_EXCLUDES,
|
||
|
"params_overrides" => static::PARAMS_OVERRIDES,
|
||
|
"params" => static::PARAMS,
|
||
|
"invalid_msg" => q(static::INVALID_MSG),
|
||
|
"submit" => A::with(static::SUBMIT),
|
||
|
"control_prefix" => q(static::CONTROL_PREFIX),
|
||
|
"control_suffix" => q(static::CONTROL_SUFFIX),
|
||
|
"submitted_key" => static::SUBMITTED_KEY,
|
||
|
]);
|
||
|
[$params, $attrs] = $this->splitParametrableParams($params);
|
||
|
if ($attrs) A::merge($params["attrs"], $attrs);
|
||
|
$this->initParametrableParams($params);
|
||
|
if ($this->ppAutoloadParams) $this->loadParams();
|
||
|
if ($this->ppAutoprint) $this->print();
|
||
|
}
|
||
|
|
||
|
/** @var ?string */
|
||
|
protected $ppAction;
|
||
|
|
||
|
/** @var ?string */
|
||
|
protected $ppMethod;
|
||
|
|
||
|
/** @var ?string */
|
||
|
protected $ppEnctype;
|
||
|
|
||
|
function pp_setUpload(bool $upload=true): void {
|
||
|
if ($upload) {
|
||
|
$this->ppMethod = "post";
|
||
|
$this->ppEnctype = "multipart/form-data";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** @var ?array */
|
||
|
protected $ppClass;
|
||
|
|
||
|
/** @var ?array */
|
||
|
protected $ppAttrs;
|
||
|
|
||
|
/** @var ?bool */
|
||
|
protected $ppAutohideLabel;
|
||
|
|
||
|
function isAutohideLabel(): bool {
|
||
|
return static::ALLOW_AUTOHIDE_LABEL && $this->ppAutohideLabel;
|
||
|
}
|
||
|
|
||
|
/** @var ?Metadata */
|
||
|
protected $md;
|
||
|
|
||
|
function pp_setSchema(?array $schema): void {
|
||
|
$md = null;
|
||
|
if ($schema !== null) $md = new Metadata($schema);
|
||
|
$this->md = $md;
|
||
|
}
|
||
|
|
||
|
private static $params_md;
|
||
|
|
||
|
protected static function params_md(): Metadata {
|
||
|
return md_utils::ensure_md(self::$params_md, self::PARAMS_SCHEMA);
|
||
|
}
|
||
|
|
||
|
protected static function params_ensure_schema(array &$params, ?Metadata $md=null): void {
|
||
|
$params_md = self::params_md();
|
||
|
foreach ($params as $key => &$param) {
|
||
|
$params_md->ensureSchema($param, $key);
|
||
|
A::replace_n($param, "name", $key);
|
||
|
if ($md !== null) {
|
||
|
$sfield = A::get($md->getSfields(), $key);
|
||
|
if ($sfield !== null) {
|
||
|
A::replace_n($param, "default", $sfield["default"]);
|
||
|
}
|
||
|
}
|
||
|
$controlClass = $param["control"];
|
||
|
if ($controlClass !== null) {
|
||
|
$controlClass = A::get(static::CONTROL_CLASSES, $controlClass, $controlClass);
|
||
|
$param["control"] = $controlClass;
|
||
|
}
|
||
|
}; unset($param);
|
||
|
}
|
||
|
|
||
|
/** @var ?array */
|
||
|
protected $ppParamsExcludes;
|
||
|
|
||
|
/** @var ?array */
|
||
|
protected $ppParamsOverrides;
|
||
|
|
||
|
/** @var ?array */
|
||
|
protected $ppParams;
|
||
|
|
||
|
/** @var bool des valeurs ont-elle été attribuées à des paramètres? */
|
||
|
protected $paramsValuesSet;
|
||
|
|
||
|
/** @var array fusion de $ppParams, $ppParamsExcludes et $ppParamsOverrides */
|
||
|
protected $params;
|
||
|
|
||
|
/** @var ?Alerter */
|
||
|
protected $ppAlerter;
|
||
|
|
||
|
function al(): ?Alerter {
|
||
|
$al = $this->ppAlerter;
|
||
|
if ($al === null) $al = al::get();
|
||
|
return $al;
|
||
|
}
|
||
|
|
||
|
/** @var array|string|null */
|
||
|
protected $ppInvalidMsg;
|
||
|
|
||
|
/** @var ?array */
|
||
|
protected $ppSubmit;
|
||
|
|
||
|
/** @var ?array */
|
||
|
protected $ppSubmits;
|
||
|
|
||
|
/** @var bool */
|
||
|
protected $ppAutoaddSubmit;
|
||
|
|
||
|
/** @var array contenu à afficher avant chaque élément de formulaire */
|
||
|
protected $ppControlPrefix;
|
||
|
|
||
|
function getControlPrefix(): ?array {
|
||
|
return $this->ppControlPrefix;
|
||
|
}
|
||
|
|
||
|
/** @var array contenu à afficher après chaque élément de formulaire */
|
||
|
protected $ppControlSuffix;
|
||
|
|
||
|
function getControlSuffix(): ?array {
|
||
|
return $this->ppControlSuffix;
|
||
|
}
|
||
|
|
||
|
/** @var bool */
|
||
|
protected $ppAutoprint;
|
||
|
|
||
|
/** @var ?string */
|
||
|
protected $ppSubmittedKey;
|
||
|
|
||
|
/** @var bool */
|
||
|
protected $ppAutoloadParams;
|
||
|
|
||
|
protected function afterSetParametrableParams(array $modifiedKeys, ?Metadata $md=null): void {
|
||
|
if (self::was_parametrable_param_modified($modifiedKeys, "submits")
|
||
|
&& !self::was_parametrable_param_modified($modifiedKeys, "autoadd_submit")
|
||
|
) {
|
||
|
$this->ppAutoaddSubmit = false;
|
||
|
}
|
||
|
if (self::was_parametrable_param_modified($modifiedKeys, "params", "params_excludes", "params_overrides")) {
|
||
|
$params = [];
|
||
|
if ($this->ppParams !== null) {
|
||
|
$paramsExcludes = $this->ppParamsExcludes;
|
||
|
$index = 0;
|
||
|
foreach ($this->ppParams as $key => $param) {
|
||
|
if ($key === $index) {
|
||
|
$index++;
|
||
|
$key = $param;
|
||
|
}
|
||
|
if ($paramsExcludes === null || !in_array($key, $paramsExcludes)) {
|
||
|
$params[$key] = $param;
|
||
|
}
|
||
|
}
|
||
|
self::params_ensure_schema($params, $md);
|
||
|
}
|
||
|
$paramsOverrides = $this->ppParamsOverrides;
|
||
|
if ($paramsOverrides !== null) {
|
||
|
$oparams = [];
|
||
|
$index = 0;
|
||
|
foreach ($paramsOverrides as $key => $param) {
|
||
|
if ($key === $index) {
|
||
|
$index++;
|
||
|
$key = $param;
|
||
|
}
|
||
|
$oparams[$key] = $param;
|
||
|
}
|
||
|
self::params_ensure_schema($oparams, $md);
|
||
|
foreach ($oparams as $key => $param) {
|
||
|
if (!A::has($params, $key)) {
|
||
|
$params[$key] = $param;
|
||
|
} else {
|
||
|
A::merge_nn($params[$key], $param);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
$this->paramsValuesSet = false;
|
||
|
$this->params = $params;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#############################################################################
|
||
|
## Params
|
||
|
|
||
|
private function resolveName($key, ?string &$name, &$value=null): bool {
|
||
|
$params = $this->params;
|
||
|
if (A::has($params, $key)) {
|
||
|
$param = $params[$key];
|
||
|
$name = $param["name"];
|
||
|
$value = $param["default"];
|
||
|
return true;
|
||
|
} else {
|
||
|
$name = $key;
|
||
|
$value = null;
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private function resolveNameType($key, ?string $name=null): array {
|
||
|
if ($name === null) $this->resolveName($key, $name);
|
||
|
$md = $this->md;
|
||
|
$type = $md !== null? $md->getType($key, false): null;
|
||
|
return [$name, $type];
|
||
|
}
|
||
|
|
||
|
function set($key, $value, ?string $name=null): self {
|
||
|
$unset = false;
|
||
|
/** @var IType $type */
|
||
|
[$name, $type] = $this->resolveNameType($key, $name);
|
||
|
if ($type !== null) {
|
||
|
if ($type->isUndef($value)) $unset = true;
|
||
|
else $type->verifix($value, $this->results[$name]);
|
||
|
}
|
||
|
$this->paramsValuesSet = true;
|
||
|
if ($unset) $this->_del($key);
|
||
|
else $this->_set($key, $value);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
protected function paramExists(string $name): bool {
|
||
|
return F::has($name);
|
||
|
}
|
||
|
|
||
|
protected function paramGet($key, ?string $name, &$value): bool {
|
||
|
/** @var IType $type */
|
||
|
[$name, $type] = $this->resolveNameType($key, $name);
|
||
|
$value = F::get($name);
|
||
|
if ($value === null) return false;
|
||
|
if ($type !== null) {
|
||
|
if ($type->isTrim()) $value = trim($value);
|
||
|
if ($value === "") $type->verifixReplaceEmpty($value);
|
||
|
if ($value === false) $type->verifixReplaceFalse($value);
|
||
|
if ($value === null) $type->verifixReplaceNull($value);
|
||
|
if ($type->isUndef($value)) return false;
|
||
|
elseif ($value === null && !$type->isAllowNull()) return false;
|
||
|
elseif ($value === false && !$type->isAllowFalse()) return false;
|
||
|
elseif ($value === "" && !$type->isAllowEmpty()) $value = null;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
protected $submitted;
|
||
|
|
||
|
protected function checkSubmitted(): ?bool {
|
||
|
$submittedKey = $this->ppSubmittedKey;
|
||
|
if ($submittedKey !== null) {
|
||
|
if ($this->paramGet($submittedKey, null, $value)) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
$submitted = null;
|
||
|
foreach (A::keys($this->params) as $key) {
|
||
|
if ($this->paramGet($key, null, $value)) {
|
||
|
if ($submitted === null) $submitted = true;
|
||
|
} else {
|
||
|
$submitted = false;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return $submitted;
|
||
|
}
|
||
|
|
||
|
/** tester si le formulaire a été soumis */
|
||
|
function isSubmitted(): bool {
|
||
|
if ($this->submitted === null) {
|
||
|
$this->submitted = boolval($this->checkSubmitted());
|
||
|
}
|
||
|
return $this->submitted;
|
||
|
}
|
||
|
|
||
|
function setSubmitted(bool $submitted=true): void {
|
||
|
$this->submitted = $submitted;
|
||
|
}
|
||
|
|
||
|
function loadParams(?array $keys=null): self {
|
||
|
$md = $this->md;
|
||
|
if ($this->ppParams === null && $md !== null) {
|
||
|
$this->ppParams = $md->getKeys();
|
||
|
$this->afterSetParametrableParams(["params"], $md);
|
||
|
}
|
||
|
$params = $this->params;
|
||
|
if ($keys === null && $params !== null) {
|
||
|
$keys = array_keys($params);
|
||
|
}
|
||
|
$submitted = $this->submitted;
|
||
|
if ($keys !== null) {
|
||
|
$submitted = $this->checkSubmitted();
|
||
|
foreach ($keys as $key) {
|
||
|
$this->resolveName($key, $name, $value);
|
||
|
if ($this->paramExists($name)) {
|
||
|
if ($submitted === null) $submitted = true;
|
||
|
if ($this->paramGet($key, $name, $value)) {
|
||
|
$this->set($key, $value, $name);
|
||
|
} else {
|
||
|
#XXX?
|
||
|
#$this->del($key);
|
||
|
}
|
||
|
} elseif ($value !== null) {
|
||
|
$this->set($key, $value, $name);
|
||
|
} else {
|
||
|
#XXX
|
||
|
#$this->del($key);
|
||
|
}
|
||
|
}
|
||
|
if ($submitted === null) $submitted = false;
|
||
|
}
|
||
|
$this->paramsValuesSet = true;
|
||
|
$this->submitted = $submitted;
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function autoloadParams(): self {
|
||
|
if (!$this->paramsValuesSet) $this->loadParams();
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function resetParams(string ...$keys): void {
|
||
|
foreach ($keys as $key) {
|
||
|
$this->_del($key);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** retourner $params avec les $key traduits en $name */
|
||
|
function bp(?array $params=null): array {
|
||
|
$result = [];
|
||
|
if ($params !== null) {
|
||
|
$index = 0;
|
||
|
foreach ($params as $key => $value) {
|
||
|
if ($key === $index) {
|
||
|
$index++;
|
||
|
$key = $value;
|
||
|
$value = $this->get($key);
|
||
|
}
|
||
|
$this->resolveName($key, $name);
|
||
|
$result[$name] = $value;
|
||
|
}
|
||
|
}
|
||
|
return $result;
|
||
|
}
|
||
|
|
||
|
#############################################################################
|
||
|
## Validation
|
||
|
|
||
|
/** @var array */
|
||
|
protected $results;
|
||
|
|
||
|
function setInvalid(string $key, ?string $message=null): void {
|
||
|
if ($message === null) $message = "Cette valeur est invalide";
|
||
|
$this->resolveName($key, $name);
|
||
|
$value = A::get($this->data, $key);
|
||
|
$this->results[$name] = [
|
||
|
"valid" => false,
|
||
|
"value" => $value,
|
||
|
"error" => $message,
|
||
|
"exception" => null,
|
||
|
"orig" => $value,
|
||
|
"parsed" => null,
|
||
|
"remains" => null,
|
||
|
];
|
||
|
}
|
||
|
|
||
|
function _getResult(string $name): ?array {
|
||
|
return A::get($this->results, $name);
|
||
|
}
|
||
|
|
||
|
function getFirstInvalid(): ?string {
|
||
|
if ($this->results !== null) {
|
||
|
foreach ($this->results as $name => $result) {
|
||
|
if (!$result["valid"]) return $name;
|
||
|
}
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
function isValidForm(): bool {
|
||
|
# si le formulaire n'a pas été soumis, il ne peut pas être valide
|
||
|
$valid = false;
|
||
|
if ($this->isSubmitted() && $this->results !== null) {
|
||
|
foreach ($this->results as $result) {
|
||
|
if (!$result["valid"]) return false;
|
||
|
$valid = true;
|
||
|
}
|
||
|
}
|
||
|
return $valid;
|
||
|
}
|
||
|
|
||
|
function isValid(string $key, ?array &$result=null): bool {
|
||
|
$this->resolveName($key, $name);
|
||
|
$result = A::get($this->results, $name);
|
||
|
return $result === null || $result["valid"];
|
||
|
}
|
||
|
|
||
|
#############################################################################
|
||
|
## Form
|
||
|
|
||
|
function printAlert(): void {
|
||
|
$alerter = $this->ppAlerter;
|
||
|
if ($alerter !== null) $alerter->print();
|
||
|
$invalidMsg = $this->ppInvalidMsg;
|
||
|
if ($invalidMsg && $this->isSubmitted() && !$this->isValidForm()) {
|
||
|
if ($alerter !== null) $alerter->pwarning($invalidMsg);
|
||
|
else msg::warning($invalidMsg);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function printStart(): void {
|
||
|
vo::write($this->getStart());
|
||
|
}
|
||
|
|
||
|
function getStart(): array {
|
||
|
return v::start("form", [
|
||
|
"action" => $this->ppAction,
|
||
|
"method" => $this->ppMethod,
|
||
|
"enctype" => $this->ppEnctype,
|
||
|
"class" => [static::FORM_CLASS, $this->ppClass],
|
||
|
$this->ppAttrs,
|
||
|
$this->form(),
|
||
|
]);
|
||
|
}
|
||
|
|
||
|
protected function form(): ?array {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
function printEnd(): void {
|
||
|
vo::write($this->getEnd());
|
||
|
}
|
||
|
|
||
|
function getEnd(): array {
|
||
|
return v::end("form");
|
||
|
}
|
||
|
|
||
|
#############################################################################
|
||
|
## Controls
|
||
|
|
||
|
/** @var ?array */
|
||
|
protected $hiddenControls;
|
||
|
|
||
|
function addHiddenControl($control, ?string $name=null): self {
|
||
|
A::set($this->hiddenControls, $name, $control);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/** @var ?array */
|
||
|
protected $controls;
|
||
|
|
||
|
function addControl($control, ?string $name=null): self {
|
||
|
A::set($this->controls, $name, $control);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
protected function buildControl(?array $param, ?string $key=null, ?string $controlClass=null, ?array $params=null): array {
|
||
|
if ($param === null) $param = A::get($this->params, $key);
|
||
|
if ($param !== null) {
|
||
|
[$key, $name, $default, $value, $label, $defaultControlClass,
|
||
|
] = A::getdels($param, array_keys(self::PARAMS_SCHEMA));
|
||
|
if ($controlClass === null && $defaultControlClass !== null) {
|
||
|
$controlClass = $defaultControlClass;
|
||
|
}
|
||
|
} else {
|
||
|
$name = $key;
|
||
|
$default = $value = null;
|
||
|
$label = null;
|
||
|
if ($controlClass === null) $controlClass = ControlText::class;
|
||
|
}
|
||
|
|
||
|
$md = $this->md;
|
||
|
$type = $md !== null? $md->getType($key, false): null;
|
||
|
$sfields = $md !== null? $md->getSfields(): null;
|
||
|
if ($controlClass === null) {
|
||
|
$haveItems = A::has($param, "items") || A::has($param, "items_func");
|
||
|
$is2States = $type !== null && $type->is2States();
|
||
|
if ($haveItems) $controlClass = ControlSelect::class;
|
||
|
elseif ($is2States) $controlClass = ControlCheckbox::class;
|
||
|
else $controlClass = ControlText::class;
|
||
|
}
|
||
|
|
||
|
[$controlType, $firstKey] = A::get(static::CONTROL_TYPES, $controlClass, self::NV);
|
||
|
|
||
|
if ($label === null && $md !== null) {
|
||
|
$label = A::_pget($sfields, [$key, "title"]);
|
||
|
}
|
||
|
if ($label === null) $label = $key;
|
||
|
if ($firstKey !== null) $param[$firstKey] = $label;
|
||
|
|
||
|
switch ($controlType) {
|
||
|
case self::NVC:
|
||
|
$param["name"] = $name;
|
||
|
if ($value === null) $value = $default;
|
||
|
if ($value === null) $value = 1;
|
||
|
$currentValue = $this->get($key);
|
||
|
if ($type !== null) {
|
||
|
#XXX la nécessité de formatter la valeur dépend-elle du contrôle utilisé?!
|
||
|
$value = $type->format($value);
|
||
|
$currentValue = $type->format($currentValue);
|
||
|
}
|
||
|
$param["value"] = $value;
|
||
|
$param["checked"] = $currentValue === $value;
|
||
|
break;
|
||
|
case self::NV:
|
||
|
if ($value === null) $value = $this->get($key, $default);
|
||
|
if ($type !== null) $value = $type->format($value);
|
||
|
$param["value"] = $value;
|
||
|
case self::N:
|
||
|
$param["name"] = $name;
|
||
|
}
|
||
|
|
||
|
if ($params === null) $params = $param;
|
||
|
else A::update_n($params, $param);
|
||
|
return [new $controlClass($this, $params), $name];
|
||
|
}
|
||
|
|
||
|
private function _prepareControls(): ?array {
|
||
|
if ($this->params === null) $this->loadParams();
|
||
|
A::ensure_array($this->controls);
|
||
|
return $this->params;
|
||
|
}
|
||
|
|
||
|
function autoaddControl(string $key): self {
|
||
|
$params = $this->_prepareControls();
|
||
|
if ($key == "") {
|
||
|
# submit
|
||
|
$this->addSubmit(null, $this->ppSubmit);
|
||
|
} elseif (A::has($this->ppSubmits, $key)) {
|
||
|
$submit = $this->ppSubmits[$key];
|
||
|
A::replace_n($submit, "id", $key);
|
||
|
$this->addSubmit(null, $submit);
|
||
|
} else {
|
||
|
$param = A::get($params, $key);
|
||
|
if ($param !== null) {
|
||
|
[$control, $name] = $this->buildControl($param);
|
||
|
$this->addControl($control, $name);
|
||
|
}
|
||
|
}
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function autoaddControls(?bool $autoaddSubmit=null): self {
|
||
|
$params = $this->_prepareControls();
|
||
|
if ($params !== null) {
|
||
|
foreach ($params as $param) {
|
||
|
[$control, $name] = $this->buildControl($param);
|
||
|
if (A::has($this->hiddenControls, $name)) continue;
|
||
|
if (A::has($this->controls, $name)) continue;
|
||
|
$this->addControl($control, $name);
|
||
|
}
|
||
|
}
|
||
|
if ($autoaddSubmit === null) $autoaddSubmit = $this->ppAutoaddSubmit;
|
||
|
if ($autoaddSubmit && !A::has($this->controls, "")) {
|
||
|
$this->addSubmit(null, $this->ppSubmit);
|
||
|
}
|
||
|
$submits = $this->ppSubmits;
|
||
|
if ($submits !== null) {
|
||
|
foreach ($submits as $key => $submit) {
|
||
|
if (!A::has($this->controls, $key)) {
|
||
|
A::replace_n($submit, "id", $key);
|
||
|
$this->addSubmit(null, $submit);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function printControls(): void {
|
||
|
if ($this->controls === null) $this->autoaddControls();
|
||
|
vo::write($this->hiddenControls);
|
||
|
vo::write($this->controls);
|
||
|
}
|
||
|
|
||
|
protected function fixControlParams(?array &$params): void {
|
||
|
}
|
||
|
protected function fixControl(Control $control): Control {
|
||
|
return $control;
|
||
|
}
|
||
|
|
||
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
# section
|
||
|
|
||
|
/** @var Section */
|
||
|
protected $section;
|
||
|
|
||
|
function startSection(?array $params=null): void {
|
||
|
}
|
||
|
function endSection(): void {
|
||
|
}
|
||
|
|
||
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
# group
|
||
|
|
||
|
/** @var Group */
|
||
|
protected $group;
|
||
|
|
||
|
function startGroup(?array $params=null): void {
|
||
|
}
|
||
|
function endGroup(): void {
|
||
|
}
|
||
|
|
||
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
# hidden
|
||
|
|
||
|
function hidden(string $name, $value, ?array $params=null): ControlHidden {
|
||
|
A::set_nz($params, "name", $name);
|
||
|
A::set_nz($params, "value", $value);
|
||
|
return new ControlHidden($this, $params);
|
||
|
}
|
||
|
|
||
|
function addHidden(string $name, $value, ?array $params=null): self {
|
||
|
$this->addHiddenControl($this->hidden($name, $value, $params), $name);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function autoaddHidden(string $key, ?array $params=null): self {
|
||
|
$this->addHiddenControl(...$this->buildControl(null, $key, ControlHidden::class, $params));
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function printHidden(string $name, $value, ?array $params=null): void {
|
||
|
vo::write($this->hidden($name, $value, $params));
|
||
|
}
|
||
|
|
||
|
function hiddens(?array $values, ?array $names=null): array {
|
||
|
if ($values === null) return [];
|
||
|
if ($names === null) $names = array_keys($values);
|
||
|
$vs = [];
|
||
|
foreach ($names as $name) {
|
||
|
$value = A::get($values, $name);
|
||
|
$vs[] = $this->hidden($name, $value);
|
||
|
}
|
||
|
return $vs;
|
||
|
}
|
||
|
|
||
|
function addHiddens(?array $values, ?array $names=null): self {
|
||
|
if ($values !== null) {
|
||
|
if ($names === null) $names = array_keys($values);
|
||
|
foreach ($names as $name) {
|
||
|
$value = A::get($values, $name);
|
||
|
$this->addHiddenControl($this->hidden($name, $value), $name);
|
||
|
}
|
||
|
}
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function printHiddens(?array $values, ?array $names=null): void {
|
||
|
vo::write($this->hiddens($values, $names));
|
||
|
}
|
||
|
|
||
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
# fixed
|
||
|
|
||
|
function fixed($label, string $name, $value, ?array $params=null): ControlFixed {
|
||
|
A::set_nz($params, "label", $label);
|
||
|
A::set_nz($params, "name", $name);
|
||
|
A::set_nz($params, "value", $value);
|
||
|
$this->fixControlParams($params);
|
||
|
return $this->fixControl(new ControlFixed($this, $params));
|
||
|
}
|
||
|
|
||
|
function addFixed($label, string $name, $value, ?array $params=null): self {
|
||
|
$this->addControl($this->fixed($label, $name, $value, $params), $name);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function autoaddFixed(string $key, ?array $params=null): self {
|
||
|
$this->addControl(...$this->buildControl(null, $key, ControlFixed::class, $params));
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function printFixed($label, string $name, $value, ?array $params=null): void {
|
||
|
vo::write($this->fixed($label, $name, $value, $params));
|
||
|
}
|
||
|
|
||
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
# input
|
||
|
|
||
|
function text($label, string $name, $value, ?array $params=null): ControlText {
|
||
|
A::set_nz($params, "label", $label);
|
||
|
A::set_nz($params, "name", $name);
|
||
|
A::set_nz($params, "value", $value);
|
||
|
$this->fixControlParams($params);
|
||
|
return $this->fixControl(new ControlText($this, $params));
|
||
|
}
|
||
|
|
||
|
function addText($label, string $name, $value, ?array $params=null): self {
|
||
|
$this->addControl($this->text($label, $name, $value, $params), $name);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function autoaddText(string $key, ?array $params=null): self {
|
||
|
$this->addControl(...$this->buildControl(null, $key, ControlText::class, $params));
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function printText($label, string $name, $value, ?array $params=null): void {
|
||
|
vo::write($this->text($label, $name, $value, $params));
|
||
|
}
|
||
|
|
||
|
function password($label, string $name, $value, ?array $params=null): ControlPassword {
|
||
|
A::set_nz($params, "label", $label);
|
||
|
A::set_nz($params, "name", $name);
|
||
|
A::set_nz($params, "value", $value);
|
||
|
$this->fixControlParams($params);
|
||
|
return $this->fixControl(new ControlPassword($this, $params));
|
||
|
}
|
||
|
|
||
|
function addPassword($label, string $name, $value, ?array $params=null): self {
|
||
|
$this->addControl($this->password($label, $name, $value, $params), $name);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function autoaddPassword(string $key, ?array $params=null): self {
|
||
|
$this->addControl(...$this->buildControl(null, $key, ControlPassword::class, $params));
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function printPassword($label, string $name, $value, ?array $params=null): void {
|
||
|
vo::write($this->password($label, $name, $value, $params));
|
||
|
}
|
||
|
|
||
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
# select
|
||
|
|
||
|
function select($label, string $name, $value, ?array $params=null): ControlSelect {
|
||
|
A::set_nz($params, "label", $label);
|
||
|
A::set_nz($params, "name", $name);
|
||
|
A::set_nz($params, "value", $value);
|
||
|
$this->fixControlParams($params);
|
||
|
return $this->fixControl(new ControlSelect($this, $params));
|
||
|
}
|
||
|
|
||
|
function addSelect($label, string $name, $value, ?array $params=null): self {
|
||
|
$this->addControl($this->select($label, $name, $value, $params), $name);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function autoaddSelect(string $key, ?array $params=null): self {
|
||
|
$this->addControl(...$this->buildControl(null, $key, ControlSelect::class, $params));
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function printSelect($label, string $name, $value, ?array $params=null): void {
|
||
|
vo::write($this->select($label, $name, $value, $params));
|
||
|
}
|
||
|
|
||
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
# checkbox
|
||
|
|
||
|
function checkbox($text, string $name, $value, ?bool $checked=null, ?array $params=null): ControlCheckbox {
|
||
|
A::set_nz($params, "text", $text);
|
||
|
A::set_nz($params, "name", $name);
|
||
|
A::set_nz($params, "value", $value);
|
||
|
A::set_nz($params, "checked", $checked);
|
||
|
$this->fixControlParams($params);
|
||
|
return $this->fixControl(new ControlCheckbox($this, $params));
|
||
|
}
|
||
|
|
||
|
function addCheckbox($text, string $name, $value, ?bool $checked=null, ?array $params=null): self {
|
||
|
$this->addControl($this->checkbox($text, $name, $value, $checked, $params), $name);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function autoaddCheckbox(string $key, ?array $params=null): self {
|
||
|
$this->addControl(...$this->buildControl(null, $key, ControlCheckbox::class, $params));
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function printCheckbox($text, string $name, $value, ?bool $checked=null, ?array $params=null): void {
|
||
|
vo::write($this->checkbox($text, $name, $value, $checked, $params));
|
||
|
}
|
||
|
|
||
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
# radiobutton
|
||
|
|
||
|
function radiobutton($text, string $name, $value, ?bool $checked=null, ?array $params=null): ControlRadiobutton {
|
||
|
A::set_nz($params, "text", $text);
|
||
|
A::set_nz($params, "name", $name);
|
||
|
A::set_nz($params, "value", $value);
|
||
|
A::set_nz($params, "checked", $checked);
|
||
|
$this->fixControlParams($params);
|
||
|
return $this->fixControl(new ControlRadiobutton($this, $params));
|
||
|
}
|
||
|
|
||
|
function addRadiobutton($text, string $name, $value, ?bool $checked=null, ?array $params=null): self {
|
||
|
$this->addControl($this->radiobutton($text, $name, $value, $checked, $params), $name);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function autoaddRadiobutton(string $key, ?array $params=null): self {
|
||
|
$this->addControl(...$this->buildControl(null, $key, ControlRadiobutton::class, $params));
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function printRadiobutton($text, string $name, $value, ?bool $checked=null, ?array $params=null): void {
|
||
|
vo::write($this->radiobutton($text, $name, $value, $checked, $params));
|
||
|
}
|
||
|
|
||
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
# textarea
|
||
|
|
||
|
function textarea($label, string $name, $value, ?array $params=null): ControlTextarea {
|
||
|
A::set_nz($params, "label", $label);
|
||
|
A::set_nz($params, "name", $name);
|
||
|
A::set_nz($params, "value", $value);
|
||
|
$this->fixControlParams($params);
|
||
|
return $this->fixControl(new ControlTextarea($this, $params));
|
||
|
}
|
||
|
|
||
|
function addTextarea($label, string $name, $value, ?array $params=null): self {
|
||
|
$this->addControl($this->textarea($label, $name, $value, $params), $name);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function autoaddTextarea(string $key, ?array $params=null): self {
|
||
|
$this->addControl(...$this->buildControl(null, $key, ControlTextarea::class, $params));
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function printTextarea($label, string $name, $value, ?array $params=null): void {
|
||
|
vo::write($this->textarea($label, $name, $value, $params));
|
||
|
}
|
||
|
|
||
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
# file
|
||
|
|
||
|
function file($label, string $name, ?array $params=null): ControlFile {
|
||
|
A::set_nz($params, "label", $label);
|
||
|
A::set_nz($params, "name", $name);
|
||
|
$this->fixControlParams($params);
|
||
|
return $this->fixControl(new ControlFile($this, $params));
|
||
|
}
|
||
|
|
||
|
function addFile($label, string $name, ?array $params=null): self {
|
||
|
$this->addControl($this->file($label, $name, $params), $name);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function autoaddFile(string $key, ?array $params=null): self {
|
||
|
$this->addControl(...$this->buildControl(null, $key, ControlFile::class, $params));
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function printFile($label, string $name, ?array $params=null): void {
|
||
|
vo::write($this->file($label, $name, $params));
|
||
|
}
|
||
|
|
||
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
# submit
|
||
|
|
||
|
function submit($submit, ?array $params=null): ControlSubmit {
|
||
|
A::set_nz($params, "submit", $submit);
|
||
|
$this->fixControlParams($params);
|
||
|
return $this->fixControl(new ControlSubmit($this, $params));
|
||
|
}
|
||
|
|
||
|
function addSubmit($submit, ?array $params=null): self {
|
||
|
$name = A::get($params, "id");
|
||
|
if ($name === null) A::get($params, "name");
|
||
|
if ($name === null) $name = "";
|
||
|
$this->addControl($this->submit($submit, $params), $name);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function autoaddSubmit(string $key, ?array $params=null): self {
|
||
|
$this->addControl(...$this->buildControl(null, $key, ControlSubmit::class, $params));
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function printSubmit($submit, ?array $params=null): void {
|
||
|
vo::write($this->submit($submit, $params));
|
||
|
}
|
||
|
|
||
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
# reset
|
||
|
|
||
|
function reset($reset, ?array $params=null): ControlReset {
|
||
|
A::set_nz($params, "reset", $reset);
|
||
|
$this->fixControlParams($params);
|
||
|
return $this->fixControl(new ControlReset($this, $params));
|
||
|
}
|
||
|
|
||
|
function addReset($reset, ?array $params=null): self {
|
||
|
$name = A::get($params, "name", "");
|
||
|
$this->addControl($this->reset($reset, $params), $name);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function autoaddReset(string $key, ?array $params=null): self {
|
||
|
$this->addControl(...$this->buildControl(null, $key, ControlReset::class, $params));
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
function printReset($reset, ?array $params=null): void {
|
||
|
vo::write($this->reset($reset, $params));
|
||
|
}
|
||
|
|
||
|
#############################################################################
|
||
|
|
||
|
function print(): void {
|
||
|
$this->autoloadParams();
|
||
|
$this->printAlert();
|
||
|
$this->printStart();
|
||
|
$this->printControls();
|
||
|
$this->printEnd();
|
||
|
}
|
||
|
}
|