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