<?php
/**
* @class form
* @brief HTML Forms creation helpers
*
* @package Clearbricks
* @subpackage Common
*
* @copyright Olivier Meunier & Association Dotclear
* @copyright GPL-2.0-only
*/
class form
{
/**
* return id and name from given argument
*
* @param string|array $nid input argument
* @param string $name returned name
* @param string $id returned id
*
* @static
* @access private
*/
private static function getNameAndId($nid, &$name, &$id): void
{
if (is_array($nid)) {
$name = $nid[0];
$id = !empty($nid[1]) ? $nid[1] : null;
} else {
$name = $id = $nid;
}
}
/**
* return an associative array of optional parameters of a class method
*
* @param string $class class name
* @param string $method method name
* @return array
*
* @static
* @access private
*/
private static function getDefaults(string $class, string $method): array
{
$options = [];
$reflect = new ReflectionMethod($class, $method);
foreach ($reflect->getParameters() as $param) {
if ($param->isOptional()) {
$options[$param->getName()] = $param->getDefaultValue();
}
}
return $options;
}
/**
* Select Box
*
* Returns HTML code for a select box.
* **$nid** could be a string or an array of name and ID.
* **$data** is an array with option titles keys and values in values
* or an array of object of type {@link formSelectOption}. If **$data** is an array of
* arrays, optgroups will be created.
*
* **$default** could be a string or an associative array of any of optional parameters:
*
* ```php
* form::combo(['name', 'id'], $data, ['class' => 'maximal', 'extra_html' => 'data-language="php"']);
* ```
*
* @uses formSelectOption
*
* @param string|array $nid Element ID and name
* @param mixed $data Select box data
* @param mixed $default Default value in select box | associative array of optional parameters
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
*
* @return string
*
* @static
*/
public static function combo(
$nid,
$data,
$default = '',
?string $class = '',
?string $tabindex = '',
bool $disabled = false,
?string $extra_html = ''
): string {
self::getNameAndId($nid, $name, $id);
if (func_num_args() > 2 && is_array($default)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
$args = array_merge($options, array_intersect_key($default, $options));
extract($args);
}
return '<select name="' . $name . '" ' .
($id ? 'id="' . $id . '" ' : '') .
($class ? 'class="' . $class . '" ' : '') .
($tabindex ? 'tabindex="' . strval((int) $tabindex) . '" ' : '') .
($disabled ? 'disabled ' : '') .
$extra_html .
'>' . "\n" .
self::comboOptions($data, $default) .
'</select>' . "\n";
}
private static function comboOptions(array $data, $default): string
{
$res = '';
$option = '<option value="%1$s"%3$s>%2$s</option>' . "\n";
$optgroup = '<optgroup label="%1$s">' . "\n" . '%2$s' . "</optgroup>\n";
foreach ($data as $k => $v) {
if (is_array($v)) {
$res .= sprintf($optgroup, $k, self::comboOptions($v, $default));
} elseif ($v instanceof formSelectOption) {
$res .= $v->render($default);
} else {
$s = ((string) $v == (string) $default) ? ' selected="selected"' : '';
$res .= sprintf($option, $v, $k, $s);
}
}
return $res;
}
/**
* Radio button
*
* Returns HTML code for a radio button.
* $nid could be a string or an array of name and ID.
* $checked could be a boolean or an associative array of any of optional parameters
*
* @param string|array $nid Element ID and name
* @param mixed $value Element value
* @param mixed $checked True if checked | associative array of optional parameters
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
*
* @return string
*
* @static
*/
public static function radio(
$nid,
$value,
$checked = false,
?string $class = '',
?string $tabindex = '',
bool $disabled = false,
?string $extra_html = ''
): string {
self::getNameAndId($nid, $name, $id);
if (func_num_args() > 2 && is_array($checked)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
$args = array_merge($options, array_intersect_key($checked, $options));
extract($args);
}
return '<input type="radio" name="' . $name . '" value="' . $value . '" ' .
($id ? 'id="' . $id . '" ' : '') .
($checked ? 'checked ' : '') .
($class ? 'class="' . $class . '" ' : '') .
($tabindex ? 'tabindex="' . strval((int) $tabindex) . '" ' : '') .
($disabled ? 'disabled ' : '') .
$extra_html .
'/>' . "\n";
}
/**
* Checkbox
*
* Returns HTML code for a checkbox.
* $nid could be a string or an array of name and ID.
* $checked could be a boolean or an associative array of any of optional parameters
*
* @param string|array $nid Element ID and name
* @param mixed $value Element value
* @param mixed $checked True if checked | associative array of optional parameters
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
*
* @return string
*
* @static
*/
public static function checkbox(
$nid,
$value,
$checked = false,
?string $class = '',
?string $tabindex = '',
bool $disabled = false,
?string $extra_html = ''
): string {
self::getNameAndId($nid, $name, $id);
if (func_num_args() > 2 && is_array($checked)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
$args = array_merge($options, array_intersect_key($checked, $options));
extract($args);
}
return '<input type="checkbox" name="' . $name . '" value="' . $value . '" ' .
($id ? 'id="' . $id . '" ' : '') .
($checked ? 'checked ' : '') .
($class ? 'class="' . $class . '" ' : '') .
($tabindex ? 'tabindex="' . strval((int) $tabindex) . '" ' : '') .
($disabled ? 'disabled ' : '') .
$extra_html .
' />' . "\n";
}
/**
* Input field
*
* Returns HTML code for an input field.
* $nid could be a string or an array of name and ID.
* $default could be a string or an associative array of any of optional parameters
*
* @param string|array $nid Element ID and name
* @param integer $size Element size
* @param integer $max Element maxlength
* @param mixed $default Element value | associative array of optional parameters
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
* @param string $type Input type
* @param string $autocomplete Autocomplete attributes if relevant
*
* @return string
*
* @static
*/
public static function field(
$nid,
?int $size,
?int $max,
$default = '',
?string $class = '',
?string $tabindex = '',
bool $disabled = false,
?string $extra_html = '',
bool $required = false,
?string $type = 'text',
?string $autocomplete = ''
): string {
self::getNameAndId($nid, $name, $id);
if (func_num_args() > 3 && is_array($default)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
$args = array_merge($options, array_intersect_key($default, $options));
extract($args);
}
return '<input type="' . $type . '" name="' . $name . '" ' .
($id ? 'id="' . $id . '" ' : '') .
($size ? 'size="' . $size . '" ' : '') .
($max ? 'maxlength="' . $max . '" ' : '') .
($default || $default === '0' ? 'value="' . $default . '" ' : '') .
($class ? 'class="' . $class . '" ' : '') .
($tabindex ? 'tabindex="' . strval((int) $tabindex) . '" ' : '') .
($disabled ? 'disabled ' : '') .
($required ? 'required ' : '') .
($autocomplete ? 'autocomplete="' . $autocomplete . '" ' : '') .
$extra_html .
' />' . "\n";
}
/**
* Password field
*
* Returns HTML code for a password field.
* $nid could be a string or an array of name and ID.
* $default could be a string or an associative array of any of optional parameters
*
* @uses form::field
*
* @param string|array $nid Element ID and name
* @param integer $size Element size
* @param integer $max Element maxlength
* @param mixed $default Element value | associative array of optional parameters
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
* @param string $autocomplete Autocomplete attributes if relevant (new-password/current-password)
*
* @return string
*
* @static
*/
public static function password(
$nid,
int $size,
?int $max,
$default = '',
?string $class = '',
?string $tabindex = '',
bool $disabled = false,
?string $extra_html = '',
bool $required = false,
?string $autocomplete = ''
): string {
if (func_num_args() > 3 && is_array($default)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
$args = array_merge($options, array_intersect_key($default, $options));
extract($args);
}
return self::field(
$nid,
$size,
$max,
$default,
$class,
$tabindex,
$disabled,
$extra_html,
$required,
'password',
$autocomplete
);
}
/**
* HTML5 Color field
*
* Returns HTML code for an input color field.
* $nid could be a string or an array of name and ID.
* $size could be a integer or an associative array of any of optional parameters
*
* @uses form::field
*
* @param string|array $nid Element ID and name
* @param mixed $size Element size | associative array of optional parameters
* @param integer $max Element maxlength
* @param string $default Element value
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
* @param string $autocomplete Autocomplete attributes if relevant
*
* @return string
*
* @static
*/
public static function color(
$nid,
$size = 7,
?int $max = 7,
?string $default = '',
?string $class = '',
?string $tabindex = '',
bool $disabled = false,
?string $extra_html = '',
bool $required = false,
?string $autocomplete = ''
): string {
if (func_num_args() > 1 && is_array($size)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
$args = array_merge($options, array_intersect_key($size, $options));
extract($args);
}
return self::field(
$nid,
$size,
$max,
$default,
$class,
$tabindex,
$disabled,
$extra_html,
$required,
'color',
$autocomplete
);
}
/**
* HTML5 Email field
*
* Returns HTML code for an input email field.
* $nid could be a string or an array of name and ID.
* $size could be a integer or an associative array of any of optional parameters
*
* @uses form::field
*
* @param string|array $nid Element ID and name
* @param mixed $size Element size | associative array of optional parameters
* @param integer $max Element maxlength
* @param string $default Element value
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
* @param string $autocomplete Autocomplete attributes if relevant
*
* @return string
*
* @static
*/
public static function email(
$nid,
$size = 20,
?int $max = 255,
?string $default = '',
?string $class = '',
?string $tabindex = '',
bool $disabled = false,
?string $extra_html = '',
bool $required = false,
?string $autocomplete = ''
): string {
if (func_num_args() > 1 && is_array($size)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
$args = array_merge($options, array_intersect_key($size, $options));
extract($args);
}
return self::field(
$nid,
$size,
$max,
$default,
$class,
$tabindex,
$disabled,
$extra_html,
$required,
'email',
$autocomplete
);
}
/**
* HTML5 URL field
*
* Returns HTML code for an input (absolute) URL field.
* $nid could be a string or an array of name and ID.
* $size could be a integer or an associative array of any of optional parameters
*
* @uses form::field
*
* @param string|array $nid Element ID and name
* @param mixed $size Element size | associative array of optional parameters
* @param integer $max Element maxlength
* @param string $default Element value
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
* @param string $autocomplete Autocomplete attributes if relevant
*
* @return string
*
* @static
*/
public static function url(
$nid,
$size = 20,
?int $max = 255,
?string $default = '',
?string $class = '',
?string $tabindex = '',
bool $disabled = false,
?string $extra_html = '',
bool $required = false,
?string $autocomplete = ''
): string {
if (func_num_args() > 1 && is_array($size)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
$args = array_merge($options, array_intersect_key($size, $options));
extract($args);
}
return self::field(
$nid,
$size,
$max,
$default,
$class,
$tabindex,
$disabled,
$extra_html,
$required,
'url',
$autocomplete
);
}
/**
* HTML5 Datetime (local) field
*
* Returns HTML code for an input datetime field.
* $nid could be a string or an array of name and ID.
* $size could be a integer or an associative array of any of optional parameters
*
* @uses form::field
*
* @param string|array $nid Element ID and name
* @param mixed $size Element size | associative array of optional parameters
* @param integer $max Element maxlength
* @param string $default Element value (in YYYY-MM-DDThh:mm format)
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
* @param string $autocomplete Autocomplete attributes if relevant
*
* @return string
*
* @static
*/
public static function datetime(
$nid,
$size = 16,
?int $max = 16,
?string $default = '',
?string $class = '',
?string $tabindex = '',
bool $disabled = false,
?string $extra_html = '',
bool $required = false,
?string $autocomplete = ''
): string {
if (func_num_args() > 1 && is_array($size)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
$args = array_merge($options, array_intersect_key($size, $options));
extract($args);
}
// Cope with unimplemented input type for some browser (type="text" + pattern + placeholder)
if (strpos(strtolower($extra_html), 'pattern=') === false) {
$extra_html .= ' pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}"';
}
if (strpos(strtolower($extra_html), 'placeholder') === false) {
$extra_html .= ' placeholder="1962-05-13T14:45"';
}
return self::field(
$nid,
$size,
$max,
$default,
$class,
$tabindex,
$disabled,
$extra_html,
$required,
'datetime-local',
$autocomplete
);
}
/**
* HTML5 Date field
*
* Returns HTML code for an input date field.
* $nid could be a string or an array of name and ID.
* $size could be a integer or an associative array of any of optional parameters
*
* @uses form::field
*
* @param string|array $nid Element ID and name
* @param mixed $size Element size | associative array of optional parameters
* @param integer $max Element maxlength
* @param string $default Element value (in YYYY-MM-DD format)
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
* @param string $autocomplete Autocomplete attributes if relevant
*
* @return string
*
* @static
*/
public static function date(
$nid,
$size = 10,
?int $max = 10,
?string $default = '',
?string $class = '',
?string $tabindex = '',
bool $disabled = false,
?string $extra_html = '',
bool $required = false,
?string $autocomplete = ''
): string {
if (func_num_args() > 1 && is_array($size)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
$args = array_merge($options, array_intersect_key($size, $options));
extract($args);
}
// Cope with unimplemented input type for some browser (type="text" + pattern + placeholder)
if (strpos(strtolower($extra_html), 'pattern=') === false) {
$extra_html .= ' pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}"';
}
if (strpos(strtolower($extra_html), 'placeholder') === false) {
$extra_html .= ' placeholder="1962-05-13"';
}
return self::field(
$nid,
$size,
$max,
$default,
$class,
$tabindex,
$disabled,
$extra_html,
$required,
'date',
$autocomplete
);
}
/**
* HTML5 Time (local) field
*
* Returns HTML code for an input time field.
* $nid could be a string or an array of name and ID.
* $size could be a integer or an associative array of any of optional parameters
*
* @uses form::field
*
* @param string|array $nid Element ID and name
* @param mixed $size Element size | associative array of optional parameters
* @param integer $max Element maxlength
* @param string $default Element value (in hh:mm format)
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
* @param string $autocomplete Autocomplete attributes if relevant
*
* @return string
*
* @static
*/
public static function time(
$nid,
$size = 5,
?int $max = 5,
?string $default = '',
?string $class = '',
?string $tabindex = '',
bool $disabled = false,
?string $extra_html = '',
bool $required = false,
?string $autocomplete = ''
): string {
if (func_num_args() > 1 && is_array($size)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
$args = array_merge($options, array_intersect_key($size, $options));
extract($args);
}
// Cope with unimplemented input type for some browser (type="text" + pattern + placeholder)
if (strpos(strtolower($extra_html), 'pattern=') === false) {
$extra_html .= ' pattern="[0-9]{2}:[0-9]{2}"';
}
if (strpos(strtolower($extra_html), 'placeholder') === false) {
$extra_html .= ' placeholder="14:45"';
}
return self::field(
$nid,
$size,
$max,
$default,
$class,
$tabindex,
$disabled,
$extra_html,
$required,
'time',
$autocomplete
);
}
/**
* HTML5 file field
*
* Returns HTML code for an input file field.
* $nid could be a string or an array of name and ID.
* $default could be a integer or an associative array of any of optional parameters
*
* @param string|array $nid Element ID and name
* @param mixed $default Element value | associative array of optional parameters
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
*
* @return string
*
* @static
*/
public static function file(
$nid,
$default = '',
?string $class = '',
?string $tabindex = '',
bool $disabled = false,
?string $extra_html = '',
bool $required = false
): string {
self::getNameAndId($nid, $name, $id);
if (func_num_args() > 1 && is_array($default)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
$args = array_merge($options, array_intersect_key($default, $options));
extract($args);
}
return '<input type="file" ' . '" name="' . $name . '" ' .
($id ? 'id="' . $id . '" ' : '') .
($default || $default === '0' ? 'value="' . $default . '" ' : '') .
($class ? 'class="' . $class . '" ' : '') .
($tabindex ? 'tabindex="' . strval((int) $tabindex) . '" ' : '') .
($disabled ? 'disabled ' : '') .
($required ? 'required ' : '') .
$extra_html .
' />' . "\n";
}
/**
* HTML5 number input field
*
* Returns HTML code for an number input field.
* $nid could be a string or an array of name and ID.
* $min could be a string or an associative array of any of optional parameters
*
* @param string|array $nid Element ID and name
* @param mixed $min Element min value (may be negative) | associative array of optional parameters
* @param integer $max Element max value (may be negative)
* @param string $default Element value
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
* @param string $autocomplete Autocomplete attributes if relevant
*
* @return string
*
* @static
*/
public static function number(
$nid,
$min = null,
?int $max = null,
?string $default = '',
?string $class = '',
?string $tabindex = '',
bool $disabled = false,
?string $extra_html = '',
bool $required = false,
?string $autocomplete = ''
): string {
self::getNameAndId($nid, $name, $id);
if (func_num_args() > 1 && is_array($min)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
$args = array_merge($options, array_intersect_key($min, $options));
extract($args);
}
return '<input type="number" name="' . $name . '" ' .
($id ? 'id="' . $id . '" ' : '') .
($min !== null ? 'min="' . $min . '" ' : '') .
($max !== null ? 'max="' . $max . '" ' : '') .
($default || $default === '0' ? 'value="' . $default . '" ' : '') .
($class ? 'class="' . $class . '" ' : '') .
($tabindex ? 'tabindex="' . strval((int) $tabindex) . '" ' : '') .
($disabled ? 'disabled ' : '') .
($required ? 'required ' : '') .
($autocomplete ? 'autocomplete="' . $autocomplete . '" ' : '') .
$extra_html .
' />' . "\n";
}
/**
* Textarea
*
* Returns HTML code for a textarea.
* $nid could be a string or an array of name and ID.
* $default could be a string or an associative array of any of optional parameters
*
* @param string|array $nid Element ID and name
* @param integer $cols Number of columns
* @param integer $rows Number of rows
* @param mixed $default Element value | associative array of optional parameters
* @param string $class Element class name
* @param string $tabindex Element tabindex
* @param boolean $disabled True if disabled
* @param string $extra_html Extra HTML attributes
* @param boolean $required Element is required
* @param string $autocomplete Autocomplete attributes if relevant
*
* @return string
*
* @static
*/
public static function textArea(
$nid,
int $cols,
int $rows,
$default = '',
?string $class = '',
?string $tabindex = '',
bool $disabled = false,
?string $extra_html = '',
bool $required = false,
?string $autocomplete = ''
): string {
self::getNameAndId($nid, $name, $id);
if (func_num_args() > 3 && is_array($default)) {
// Cope with associative array of optional parameters
$options = self::getDefaults(__CLASS__, __FUNCTION__);
$args = array_merge($options, array_intersect_key($default, $options));
extract($args);
}
return '<textarea cols="' . $cols . '" rows="' . $rows . '" name="' . $name . '" ' .
($id ? 'id="' . $id . '" ' : '') .
($tabindex != '' ? 'tabindex="' . strval((int) $tabindex) . '" ' : '') .
($class ? 'class="' . $class . '" ' : '') .
($disabled ? 'disabled ' : '') .
($required ? 'required ' : '') .
($autocomplete ? 'autocomplete="' . $autocomplete . '" ' : '') .
$extra_html . '>' . $default . '</textarea>' . "\n";
}
/**
* Hidden field
*
* Returns HTML code for an hidden field. $nid could be a string or an array of
* name and ID.
*
* @param string|array $nid Element ID and name
* @param mixed $value Element value
*
* @return string
*
* @static
*/
public static function hidden($nid, $value): string
{
self::getNameAndId($nid, $name, $id);
return '<input type="hidden" name="' . $name . '" value="' . $value . '" ' .
($id ? 'id="' . $id . '" ' : '') .
' />' . "\n";
}
}
/**
* @class formSelectOption
* @brief HTML Forms creation helpers
*
* @package Clearbricks
* @subpackage Common
*/
class formSelectOption
{
public $name; ///< string Option name
public $value; ///< mixed Option value
public $class_name; ///< string Element class name
public $html; ///< string Extra HTML attributes
/**
* sprintf template for option
* @var string $option
* @access private
*/
private $option = '<option value="%1$s"%3$s>%2$s</option>' . "\n";
/**
* Option constructor
*
* @param string $name Option name
* @param mixed $value Option value
* @param string $class_name Element class name
* @param string $html Extra HTML attributes
*/
public function __construct(string $name, $value, string $class_name = '', string $html = '')
{
$this->name = $name;
$this->value = $value;
$this->class_name = $class_name;
$this->html = $html;
}
/**
* Option renderer
*
* Returns option HTML code
*
* @param string $default Value of selected option
* @return string
*/
public function render(?string $default): string
{
$attr = $this->html ? ' ' . $this->html : '';
$attr .= $this->class_name ? ' class="' . $this->class_name . '"' : '';
if ($this->value == $default) {
$attr .= ' selected';
}
return sprintf($this->option, $this->value, $this->name, $attr) . "\n";
}
}