parent
c396101ccc
commit
d734efaffe
@ -0,0 +1,35 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts <Bob_586@Yahoo.com> |
||||||
|
* @copyright (c) 2025, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace CodeHydrater\enums; |
||||||
|
|
||||||
|
enum form_input_types: string { |
||||||
|
case none = "none"; // Don't Display control |
||||||
|
case checkbox ="checkbox"; |
||||||
|
case color = "color"; |
||||||
|
case date = "date"; |
||||||
|
case datetime = "datetime-local"; |
||||||
|
case email = "email"; |
||||||
|
case file = "file"; |
||||||
|
case hidden = "hidden"; |
||||||
|
case image = "image"; |
||||||
|
case month = "month"; |
||||||
|
case number = "number"; |
||||||
|
case password = "password"; |
||||||
|
case radio = "radio"; |
||||||
|
case range = "range"; |
||||||
|
case reset = "rest"; |
||||||
|
case search = "search"; |
||||||
|
case submit = "submit"; |
||||||
|
case tel = "tel"; |
||||||
|
case text = "text"; |
||||||
|
case time = "time"; |
||||||
|
case url = "url"; |
||||||
|
case week = "week"; |
||||||
|
} |
||||||
@ -0,0 +1,210 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts <Bob_586@Yahoo.com> |
||||||
|
* @copyright (c) 2020 Elusive, Modifications by (c) 2025 Robert Strutts |
||||||
|
* @link https://github.com/elusivecodes/FyreFormBuilder/blob/main/LICENSE |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace CodeHydrater\form_builder; |
||||||
|
|
||||||
|
use \CodeHydrater\form_builder\html_helper as Helper; |
||||||
|
use CodeHydrater\enums\form_input_types as FORM_TYPE; |
||||||
|
|
||||||
|
class html_form { |
||||||
|
private $output = ""; |
||||||
|
private bool $escape = true; |
||||||
|
|
||||||
|
public function __construct(protected Helper $html = new Helper()) {} |
||||||
|
|
||||||
|
public function get_output(): ?string { |
||||||
|
return $this->output; |
||||||
|
} |
||||||
|
|
||||||
|
public function set_escape(bool $escape = true) { |
||||||
|
$this->escape = $escape; |
||||||
|
} |
||||||
|
|
||||||
|
private function append_out(?string $out, int $tabs = 2): ?string { |
||||||
|
$this->output .= str_repeat("\t", $tabs) . $out . PHP_EOL; |
||||||
|
return $out; |
||||||
|
} |
||||||
|
|
||||||
|
public function __call(string $type, array $arguments): string { |
||||||
|
// Get all valid values |
||||||
|
$validTypes = array_column(FORM_TYPE::cases(), 'value'); |
||||||
|
if (!in_array($type, $validTypes, true)) { |
||||||
|
throw new \InvalidArgumentException( |
||||||
|
"Invalid input type: '$type'. Must be one of: " . |
||||||
|
implode(', ', $validTypes) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
if ($type === "none") { |
||||||
|
return ""; |
||||||
|
} |
||||||
|
|
||||||
|
$name = array_shift($arguments); |
||||||
|
$options = array_shift($arguments) ?? []; |
||||||
|
|
||||||
|
$options['type'] = $type; |
||||||
|
|
||||||
|
return $this->input($name, $options); |
||||||
|
} |
||||||
|
|
||||||
|
private function do_escape(string $content, array & $options): ?string { |
||||||
|
$escape = $options['escape'] ?? $this->escape; |
||||||
|
if ($escape) { |
||||||
|
return $this->html->escape($content); |
||||||
|
} |
||||||
|
return $content; |
||||||
|
} |
||||||
|
|
||||||
|
public function button(string $content = '', array $options = []): string |
||||||
|
{ |
||||||
|
$options['type'] ??= 'button'; |
||||||
|
return $this->append_out('<button'.$this->html->attributes($options).'>'.$this->do_escape($content, $options).'</button>'); |
||||||
|
} |
||||||
|
|
||||||
|
public function close(): string |
||||||
|
{ |
||||||
|
return $this->append_out('</form>', 0); |
||||||
|
} |
||||||
|
|
||||||
|
public function fieldset_close(): string |
||||||
|
{ |
||||||
|
return $this->append_out('</fieldset>'); |
||||||
|
} |
||||||
|
|
||||||
|
public function fieldset_open(array $options = []): string |
||||||
|
{ |
||||||
|
return $this->append_out('<fieldset'.$this->html->attributes($options).'>'); |
||||||
|
} |
||||||
|
|
||||||
|
public function input(string|null $name = null, array $options = []): string |
||||||
|
{ |
||||||
|
if ($name !== null) { |
||||||
|
$options['name'] ??= $name; |
||||||
|
} |
||||||
|
|
||||||
|
$options['type'] ??= 'text'; |
||||||
|
|
||||||
|
return $this->append_out('<input'.$this->html->attributes($options).' />'); |
||||||
|
} |
||||||
|
|
||||||
|
public function open_div(array $options = []): string |
||||||
|
{ |
||||||
|
return $this->append_out('<div'.$this->html->attributes($options).'>'); |
||||||
|
} |
||||||
|
|
||||||
|
public function close_div(): string |
||||||
|
{ |
||||||
|
return $this->append_out('</div>'); |
||||||
|
} |
||||||
|
|
||||||
|
public function label(string $content = '', array $options = []): string |
||||||
|
{ |
||||||
|
return $this->append_out('<label'.$this->html->attributes($options).'>'.$this->do_escape($content, $options).'</label>'); |
||||||
|
} |
||||||
|
|
||||||
|
public function legend(string $content = '', array $options = []): string |
||||||
|
{ |
||||||
|
return $this->append_out('<legend'.$this->html->attributes($options).'>'.$this->do_escape($content, $options).'</legend>'); |
||||||
|
} |
||||||
|
|
||||||
|
public function open(string|null $action = null, array $options = []): string |
||||||
|
{ |
||||||
|
if ($action !== null) { |
||||||
|
$options['action'] ??= $action; |
||||||
|
} |
||||||
|
|
||||||
|
$options['method'] ??= 'post'; |
||||||
|
$options['accept-charset'] ??= $this->html->get_charset(); |
||||||
|
|
||||||
|
return $this->append_out('<form'.$this->html->attributes($options).'>', 0); |
||||||
|
} |
||||||
|
|
||||||
|
public function open_multipart(string|null $action = null, array $options = []): string |
||||||
|
{ |
||||||
|
$options['enctype'] = 'multipart/form-data'; |
||||||
|
return $this->append_out($this->open($action, $options)); |
||||||
|
} |
||||||
|
|
||||||
|
public function select(string|null $name = null, array $options = []): string |
||||||
|
{ |
||||||
|
if ($name !== null) { |
||||||
|
$options['name'] ??= $name; |
||||||
|
} |
||||||
|
|
||||||
|
$select_options = $options['options'] ?? []; |
||||||
|
unset($options['options']); |
||||||
|
|
||||||
|
$selected = $options['value'] ?? []; |
||||||
|
unset($options['value']); |
||||||
|
|
||||||
|
if (!is_array($selected)) { |
||||||
|
$selected = [$selected]; |
||||||
|
} |
||||||
|
|
||||||
|
$html = '<select'.$this->html->attributes($options).'>'; |
||||||
|
$html .= $this->build_options($select_options, $selected); |
||||||
|
$html .= '</select>'; |
||||||
|
|
||||||
|
return $this->append_out($html); |
||||||
|
} |
||||||
|
|
||||||
|
public function select_multi(string|null $name = null, array $options = []): string |
||||||
|
{ |
||||||
|
$options[] = 'multiple'; |
||||||
|
return $this->append_out($this->select($name, $options)); |
||||||
|
} |
||||||
|
|
||||||
|
public function textarea(string|null $name = null, array $options = []): string |
||||||
|
{ |
||||||
|
if ($name !== null) { |
||||||
|
$options['name'] ??= $name; |
||||||
|
} |
||||||
|
|
||||||
|
$value = $options['value'] ?? ''; |
||||||
|
unset($options['value']); |
||||||
|
|
||||||
|
return $this->append_out('<textarea'.$this->html->attributes($options).'>'.$this->html->escape($value).'</textarea>'); |
||||||
|
} |
||||||
|
|
||||||
|
protected function build_options(array $options, array $selected): string |
||||||
|
{ |
||||||
|
$html = ''; |
||||||
|
|
||||||
|
foreach ($options as $value => $option) { |
||||||
|
if (!is_array($option)) { |
||||||
|
$option = [ |
||||||
|
'label' => $option, |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
if (array_key_exists('children', $option)) { |
||||||
|
$children = $option['children']; |
||||||
|
unset($option['children']); |
||||||
|
|
||||||
|
$html .= '<optgroup'.$this->html->attributes($option).'>'; |
||||||
|
$html .= $this->build_options($children, $selected); |
||||||
|
$html .= '</optgroup>'; |
||||||
|
} else { |
||||||
|
$option['value'] ??= $value; |
||||||
|
$label = $option['label'] ?? $option['value']; |
||||||
|
unset($option['label']); |
||||||
|
|
||||||
|
if (in_array($option['value'], $selected)) { |
||||||
|
$option[] = 'selected'; |
||||||
|
} |
||||||
|
|
||||||
|
$html .= '<option'.$this->html->attributes($option).'>'.$this->html->escape($label).'</option>'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $html; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,160 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts <Bob_586@Yahoo.com> |
||||||
|
* @copyright (c) 2020 Elusive, Modifications by (c) 2025 Robert Strutts |
||||||
|
* @link https://github.com/elusivecodes/FyreFormBuilder/blob/main/LICENSE |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace CodeHydrater\form_builder; |
||||||
|
|
||||||
|
class html_helper { |
||||||
|
|
||||||
|
protected const ATTRIBUTES_ORDER = [ |
||||||
|
'action', |
||||||
|
'class', |
||||||
|
'id', |
||||||
|
'placeholder', |
||||||
|
'required', |
||||||
|
'disabled', |
||||||
|
'readonly', |
||||||
|
'maxlength', |
||||||
|
'minlength', |
||||||
|
'min', |
||||||
|
'max', |
||||||
|
'step', |
||||||
|
'pattern', |
||||||
|
'autocomplete', |
||||||
|
'size', |
||||||
|
'name', |
||||||
|
'data-', |
||||||
|
'src', |
||||||
|
'for', |
||||||
|
'type', |
||||||
|
'href', |
||||||
|
'action', |
||||||
|
'method', |
||||||
|
'value', |
||||||
|
'title', |
||||||
|
'alt', |
||||||
|
'target', |
||||||
|
'role', |
||||||
|
'aria-', |
||||||
|
'accept-charset', |
||||||
|
'charset', |
||||||
|
'style', |
||||||
|
]; |
||||||
|
|
||||||
|
protected string $charset = 'UTF-8'; |
||||||
|
|
||||||
|
/** |
||||||
|
* Generate an attribute string. |
||||||
|
* |
||||||
|
* @param array $options The attributes. |
||||||
|
* @return string The attribute string. |
||||||
|
*/ |
||||||
|
public function attributes(array $options = []): string { |
||||||
|
unset($options['escape']); |
||||||
|
$attributes = []; |
||||||
|
|
||||||
|
foreach ($options as $key => $value) { |
||||||
|
if ($value === null) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
if (is_numeric($key)) { |
||||||
|
$key = $value; |
||||||
|
$value = null; |
||||||
|
} |
||||||
|
|
||||||
|
$key = preg_replace('/[^\w\-:.@]/', '', $key); |
||||||
|
$key = strtolower($key); |
||||||
|
|
||||||
|
if (!$key) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
if (is_bool($value)) { |
||||||
|
$value = $value ? null : 'false'; |
||||||
|
} else if (is_array($value) || is_object($value)) { |
||||||
|
$value = json_encode($value); |
||||||
|
$value = $this->escape($value); |
||||||
|
} else if ($value !== null) { |
||||||
|
$value = $this->escape((string) $value); |
||||||
|
} |
||||||
|
|
||||||
|
$attributes[$key] = $value; |
||||||
|
} |
||||||
|
|
||||||
|
if ($attributes === []) { |
||||||
|
return ''; |
||||||
|
} |
||||||
|
|
||||||
|
uksort( |
||||||
|
$attributes, |
||||||
|
static fn(string $a, string $b): int => static::attribute_index($a) <=> static::attribute_index($b) |
||||||
|
); |
||||||
|
|
||||||
|
$html = ''; |
||||||
|
|
||||||
|
foreach ($attributes as $key => $value) { |
||||||
|
if (str_contains($key, "data-") || str_contains($key, "aria-")) { |
||||||
|
// Okay, it's valid... |
||||||
|
} else { |
||||||
|
if (!in_array($key, static::ATTRIBUTES_ORDER)) { |
||||||
|
continue; // Not in the list, skip it |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if ($value !== null) { |
||||||
|
$html .= ' '.$key.'="'.$value.'"'; |
||||||
|
} else { |
||||||
|
$html .= ' '.$key; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $html; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Escape characters in a string for use in HTML. |
||||||
|
* |
||||||
|
* @param string $string The input string. |
||||||
|
* @return string The escaped string. |
||||||
|
*/ |
||||||
|
public function escape(string $string): string { |
||||||
|
return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE | \ENT_HTML5, $this->charset); |
||||||
|
} |
||||||
|
|
||||||
|
public function get_charset(): string { |
||||||
|
return $this->charset; |
||||||
|
} |
||||||
|
|
||||||
|
public function set_charset(string $charset): static { |
||||||
|
$this->charset = $charset; |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Get the index for an attribute. |
||||||
|
* |
||||||
|
* @param string $attribute The attribute name. |
||||||
|
* @return int The attribute index. |
||||||
|
*/ |
||||||
|
protected static function attribute_index(string $attribute): int { |
||||||
|
if (preg_match('/^(data|aria)-/', $attribute)) { |
||||||
|
$attribute = substr($attribute, 0, 5); |
||||||
|
} |
||||||
|
|
||||||
|
$index = array_search($attribute, static::ATTRIBUTES_ORDER); |
||||||
|
|
||||||
|
if ($index === false) { |
||||||
|
return count(static::ATTRIBUTES_ORDER); |
||||||
|
} |
||||||
|
|
||||||
|
return $index; |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue