New contact form with friendlycaptcha

This commit is contained in:
Jorit Tijsen
2026-02-24 14:04:04 +01:00
parent 6786ac7ce1
commit 3b4030113b
80 changed files with 2860 additions and 1918 deletions

View File

@@ -9,9 +9,9 @@ declare(strict_types=1);
namespace Nette\Utils;
use Nette;
use Nette\HtmlStringable;
use function is_array, is_float, is_object, is_string;
use function array_merge, array_splice, count, explode, func_num_args, html_entity_decode, htmlspecialchars, http_build_query, implode, is_array, is_bool, is_float, is_object, is_string, json_encode, max, number_format, rtrim, str_contains, str_repeat, str_replace, strip_tags, strncmp, strpbrk, substr;
use const ENT_HTML5, ENT_NOQUOTES, ENT_QUOTES;
/**
@@ -233,37 +233,30 @@ use function is_array, is_float, is_object, is_string;
*/
class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringable
{
use Nette\SmartObject;
/** @var array<string, mixed> element's attributes */
public $attrs = [];
public array $attrs = [];
/** @var bool use XHTML syntax? */
public static $xhtml = false;
/** @var array<string, int> void elements */
public static $emptyElements = [
/** void elements */
public static array $emptyElements = [
'img' => 1, 'hr' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'area' => 1, 'embed' => 1, 'keygen' => 1,
'source' => 1, 'base' => 1, 'col' => 1, 'link' => 1, 'param' => 1, 'basefont' => 1, 'frame' => 1,
'isindex' => 1, 'wbr' => 1, 'command' => 1, 'track' => 1,
];
/** @var array<int, HtmlStringable|string> nodes */
protected $children = [];
protected array $children = [];
/** @var string element's name */
private $name;
/** element's name */
private string $name = '';
/** @var bool is element empty? */
private $isEmpty;
private bool $isEmpty = false;
/**
* Constructs new HTML element.
* @param array|string $attrs element's attributes or plain text content
* @return static
*/
public static function el(?string $name = null, $attrs = null)
public static function el(?string $name = null, array|string|null $attrs = null): static
{
$el = new static;
$parts = explode(' ', (string) $name, 2);
@@ -289,7 +282,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Returns an object representing HTML text.
*/
public static function fromHtml(string $html): self
public static function fromHtml(string $html): static
{
return (new static)->setHtml($html);
}
@@ -298,7 +291,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Returns an object representing plain text.
*/
public static function fromText(string $text): self
public static function fromText(string $text): static
{
return (new static)->setText($text);
}
@@ -333,9 +326,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Changes element's name.
* @return static
*/
final public function setName(string $name, ?bool $isEmpty = null)
final public function setName(string $name, ?bool $isEmpty = null): static
{
$this->name = $name;
$this->isEmpty = $isEmpty ?? isset(static::$emptyElements[$name]);
@@ -363,9 +355,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Sets multiple attributes.
* @return static
*/
public function addAttributes(array $attrs)
public function addAttributes(array $attrs): static
{
$this->attrs = array_merge($this->attrs, $attrs);
return $this;
@@ -374,11 +365,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Appends value to element's attribute.
* @param mixed $value
* @param mixed $option
* @return static
*/
public function appendAttribute(string $name, $value, $option = true)
public function appendAttribute(string $name, mixed $value, mixed $option = true): static
{
if (is_array($value)) {
$prev = isset($this->attrs[$name]) ? (array) $this->attrs[$name] : [];
@@ -400,10 +388,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Sets element's attribute.
* @param mixed $value
* @return static
*/
public function setAttribute(string $name, $value)
public function setAttribute(string $name, mixed $value): static
{
$this->attrs[$name] = $value;
return $this;
@@ -412,9 +398,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Returns element's attribute.
* @return mixed
*/
public function getAttribute(string $name)
public function getAttribute(string $name): mixed
{
return $this->attrs[$name] ?? null;
}
@@ -422,9 +407,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Unsets element's attribute.
* @return static
*/
public function removeAttribute(string $name)
public function removeAttribute(string $name): static
{
unset($this->attrs[$name]);
return $this;
@@ -433,9 +417,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Unsets element's attributes.
* @return static
*/
public function removeAttributes(array $attributes)
public function removeAttributes(array $attributes): static
{
foreach ($attributes as $name) {
unset($this->attrs[$name]);
@@ -447,9 +430,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Overloaded setter for element's attribute.
* @param mixed $value
*/
final public function __set(string $name, $value): void
final public function __set(string $name, mixed $value): void
{
$this->attrs[$name] = $value;
}
@@ -457,9 +439,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Overloaded getter for element's attribute.
* @return mixed
*/
final public function &__get(string $name)
final public function &__get(string $name): mixed
{
return $this->attrs[$name];
}
@@ -485,9 +466,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Overloaded setter for element's attribute.
* @return mixed
*/
final public function __call(string $m, array $args)
final public function __call(string $m, array $args): mixed
{
$p = substr($m, 0, 3);
if ($p === 'get' || $p === 'set' || $p === 'add') {
@@ -516,9 +496,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Special setter for element's attribute.
* @return static
*/
final public function href(string $path, ?array $query = null)
final public function href(string $path, array $query = []): static
{
if ($query) {
$query = http_build_query($query, '', '&');
@@ -534,10 +513,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Setter for data-* attributes. Booleans are converted to 'true' resp. 'false'.
* @param mixed $value
* @return static
*/
public function data(string $name, $value = null)
public function data(string $name, mixed $value = null): static
{
if (func_num_args() === 1) {
$this->attrs['data'] = $name;
@@ -553,10 +530,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Sets element's HTML content.
* @param HtmlStringable|string $html
* @return static
*/
final public function setHtml($html)
final public function setHtml(mixed $html): static
{
$this->children = [(string) $html];
return $this;
@@ -574,10 +549,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Sets element's textual content.
* @param HtmlStringable|string|int|float $text
* @return static
*/
final public function setText($text)
final public function setText(mixed $text): static
{
if (!$text instanceof HtmlStringable) {
$text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8');
@@ -599,10 +572,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Adds new element's child.
* @param HtmlStringable|string $child Html node or raw HTML string
* @return static
*/
final public function addHtml($child)
final public function addHtml(HtmlStringable|string $child): static
{
return $this->insert(null, $child);
}
@@ -610,10 +581,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Appends plain-text string to element content.
* @param HtmlStringable|string|int|float $text
* @return static
*/
public function addText($text)
public function addText(\Stringable|string|int|null $text): static
{
if (!$text instanceof HtmlStringable) {
$text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8');
@@ -625,10 +594,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Creates and adds a new Html child.
* @param array|string $attrs element's attributes or raw HTML string
* @return static created element
*/
final public function create(string $name, $attrs = null)
final public function create(string $name, array|string|null $attrs = null): static
{
$this->insert(null, $child = static::el($name, $attrs));
return $child;
@@ -637,10 +604,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
/**
* Inserts child node.
* @param HtmlStringable|string $child Html node or raw HTML string
* @return static
*/
public function insert(?int $index, $child, bool $replace = false)
public function insert(?int $index, HtmlStringable|string $child, bool $replace = false): static
{
$child = $child instanceof self ? $child : (string) $child;
if ($index === null) { // append
@@ -661,17 +626,15 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
*/
final public function offsetSet($index, $child): void
{
$this->insert($index, $child, true);
$this->insert($index, $child, replace: true);
}
/**
* Returns child node (\ArrayAccess implementation).
* @param int $index
* @return HtmlStringable|string
*/
#[\ReturnTypeWillChange]
final public function offsetGet($index)
final public function offsetGet($index): HtmlStringable|string
{
return $this->children[$index];
}
@@ -771,16 +734,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
final public function __toString(): string
{
try {
return $this->render();
} catch (\Throwable $e) {
if (PHP_VERSION_ID >= 70400) {
throw $e;
}
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
return '';
}
return $this->render();
}
@@ -790,7 +744,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
final public function startTag(): string
{
return $this->name
? '<' . $this->name . $this->attributes() . (static::$xhtml && $this->isEmpty ? ' />' : '>')
? '<' . $this->name . $this->attributes() . '>'
: '';
}
@@ -821,11 +775,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
continue;
} elseif ($value === true) {
if (static::$xhtml) {
$s .= ' ' . $key . '="' . $key . '"';
} else {
$s .= ' ' . $key;
}
$s .= ' ' . $key;
continue;
@@ -857,14 +807,14 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
$value = (string) $value;
}
$q = strpos($value, '"') === false ? '"' : "'";
$q = str_contains($value, '"') ? "'" : '"';
$s .= ' ' . $key . '=' . $q
. str_replace(
['&', $q, '<'],
['&amp;', $q === '"' ? '&quot;' : '&#39;', self::$xhtml ? '&lt;' : '<'],
$value
['&amp;', $q === '"' ? '&quot;' : '&#39;', '<'],
$value,
)
. (strpos($value, '`') !== false && strpbrk($value, ' <>"\'') === false ? ' ' : '')
. (str_contains($value, '`') && strpbrk($value, ' <>"\'') === false ? ' ' : '')
. $q;
}