| 1 |
<?php |
| 2 |
|
| 3 |
/** |
| 4 |
* This file is part of the Nette Framework (https://nette.org) |
| 5 |
* Copyright (c) 2004 David Grudl (https://davidgrudl.com) |
| 6 |
*/ |
| 7 |
declare (strict_types=1); |
| 8 |
namespace Packetery\Nette\Forms; |
| 9 |
|
| 10 |
use Packetery\Nette; |
| 11 |
use Packetery\Nette\Utils\Html; |
| 12 |
use Packetery\Nette\Utils\Strings; |
| 13 |
/** |
| 14 |
* Forms helpers. |
| 15 |
*/ |
| 16 |
class Helpers |
| 17 |
{ |
| 18 |
use \Packetery\Nette\StaticClass; |
| 19 |
private const UNSAFE_NAMES = ['attributes', 'children', 'elements', 'focus', 'length', 'reset', 'style', 'submit', 'onsubmit', 'form', 'presenter', 'action']; |
| 20 |
/** |
| 21 |
* Extracts and sanitizes submitted form data for single control. |
| 22 |
* @param int $type type Form::DATA_TEXT, DATA_LINE, DATA_FILE, DATA_KEYS |
| 23 |
* @return string|string[] |
| 24 |
* @internal |
| 25 |
*/ |
| 26 |
public static function extractHttpData(array $data, string $htmlName, int $type) |
| 27 |
{ |
| 28 |
$name = \explode('[', \str_replace(['[]', ']', '.'], ['', '', '_'], $htmlName)); |
| 29 |
$data = \Packetery\Nette\Utils\Arrays::get($data, $name, null); |
| 30 |
$itype = $type & ~Form::DATA_KEYS; |
| 31 |
if (\substr($htmlName, -2) === '[]') { |
| 32 |
if (!\is_array($data)) { |
| 33 |
return []; |
| 34 |
} |
| 35 |
foreach ($data as $k => $v) { |
| 36 |
$data[$k] = $v = static::sanitize($itype, $v); |
| 37 |
if ($v === null) { |
| 38 |
unset($data[$k]); |
| 39 |
} |
| 40 |
} |
| 41 |
if ($type & Form::DATA_KEYS) { |
| 42 |
return $data; |
| 43 |
} |
| 44 |
return \array_values($data); |
| 45 |
} else { |
| 46 |
return static::sanitize($itype, $data); |
| 47 |
} |
| 48 |
} |
| 49 |
private static function sanitize(int $type, $value) |
| 50 |
{ |
| 51 |
if ($type === Form::DATA_TEXT) { |
| 52 |
return \is_scalar($value) ? Strings::normalizeNewLines($value) : null; |
| 53 |
} elseif ($type === Form::DATA_LINE) { |
| 54 |
return \is_scalar($value) ? Strings::trim(\strtr((string) $value, "\r\n", ' ')) : null; |
| 55 |
} elseif ($type === Form::DATA_FILE) { |
| 56 |
return $value instanceof \Packetery\Nette\Http\FileUpload ? $value : null; |
| 57 |
} else { |
| 58 |
throw new \Packetery\Nette\InvalidArgumentException('Unknown data type'); |
| 59 |
} |
| 60 |
} |
| 61 |
/** |
| 62 |
* Converts control name to HTML name. |
| 63 |
*/ |
| 64 |
public static function generateHtmlName(string $id) : string |
| 65 |
{ |
| 66 |
$name = \str_replace(\Packetery\Nette\ComponentModel\IComponent::NAME_SEPARATOR, '][', $id, $count); |
| 67 |
if ($count) { |
| 68 |
$name = \substr_replace($name, '', \strpos($name, ']'), 1) . ']'; |
| 69 |
} |
| 70 |
if (\is_numeric($name) || \in_array($name, self::UNSAFE_NAMES, \true)) { |
| 71 |
$name = '_' . $name; |
| 72 |
} |
| 73 |
return $name; |
| 74 |
} |
| 75 |
public static function exportRules(Rules $rules) : array |
| 76 |
{ |
| 77 |
$payload = []; |
| 78 |
foreach ($rules as $rule) { |
| 79 |
if (!$rule->canExport()) { |
| 80 |
if ($rule->branch) { |
| 81 |
continue; |
| 82 |
} |
| 83 |
break; |
| 84 |
} |
| 85 |
$op = $rule->validator; |
| 86 |
if (!\is_string($op)) { |
| 87 |
$op = \Packetery\Nette\Utils\Callback::toString($op); |
| 88 |
} |
| 89 |
if ($rule->branch) { |
| 90 |
$item = ['op' => ($rule->isNegative ? '~' : '') . $op, 'rules' => static::exportRules($rule->branch), 'control' => $rule->control->getHtmlName()]; |
| 91 |
if ($rule->branch->getToggles()) { |
| 92 |
$item['toggle'] = $rule->branch->getToggles(); |
| 93 |
} elseif (!$item['rules']) { |
| 94 |
continue; |
| 95 |
} |
| 96 |
} else { |
| 97 |
$msg = Validator::formatMessage($rule, \false); |
| 98 |
if ($msg instanceof \Packetery\Nette\HtmlStringable) { |
| 99 |
$msg = \html_entity_decode(\strip_tags((string) $msg), \ENT_QUOTES | \ENT_HTML5, 'UTF-8'); |
| 100 |
} |
| 101 |
$item = ['op' => ($rule->isNegative ? '~' : '') . $op, 'msg' => $msg]; |
| 102 |
} |
| 103 |
if (\is_array($rule->arg)) { |
| 104 |
$item['arg'] = []; |
| 105 |
foreach ($rule->arg as $key => $value) { |
| 106 |
$item['arg'][$key] = $value instanceof Control ? ['control' => $value->getHtmlName()] : $value; |
| 107 |
} |
| 108 |
} elseif ($rule->arg !== null) { |
| 109 |
$item['arg'] = $rule->arg instanceof Control ? ['control' => $rule->arg->getHtmlName()] : $rule->arg; |
| 110 |
} |
| 111 |
$payload[] = $item; |
| 112 |
} |
| 113 |
return $payload; |
| 114 |
} |
| 115 |
public static function createInputList(array $items, array $inputAttrs = null, array $labelAttrs = null, $wrapper = null) : string |
| 116 |
{ |
| 117 |
[$inputAttrs, $inputTag] = self::prepareAttrs($inputAttrs, 'input'); |
| 118 |
[$labelAttrs, $labelTag] = self::prepareAttrs($labelAttrs, 'label'); |
| 119 |
$res = ''; |
| 120 |
$input = Html::el(); |
| 121 |
$label = Html::el(); |
| 122 |
[$wrapper, $wrapperEnd] = $wrapper instanceof Html ? [$wrapper->startTag(), $wrapper->endTag()] : [(string) $wrapper, '']; |
| 123 |
foreach ($items as $value => $caption) { |
| 124 |
foreach ($inputAttrs as $k => $v) { |
| 125 |
$input->attrs[$k] = $v[$value] ?? null; |
| 126 |
} |
| 127 |
foreach ($labelAttrs as $k => $v) { |
| 128 |
$label->attrs[$k] = $v[$value] ?? null; |
| 129 |
} |
| 130 |
$input->value = $value; |
| 131 |
$res .= ($res === '' && $wrapperEnd === '' ? '' : $wrapper) . $labelTag . $label->attributes() . '>' . $inputTag . $input->attributes() . (Html::$xhtml ? ' />' : '>') . ($caption instanceof \Packetery\Nette\HtmlStringable ? $caption : \htmlspecialchars((string) $caption, \ENT_NOQUOTES, 'UTF-8')) . '</label>' . $wrapperEnd; |
| 132 |
} |
| 133 |
return $res; |
| 134 |
} |
| 135 |
public static function createSelectBox(array $items, array $optionAttrs = null, $selected = null) : Html |
| 136 |
{ |
| 137 |
if ($selected !== null) { |
| 138 |
$optionAttrs['selected?'] = $selected; |
| 139 |
} |
| 140 |
[$optionAttrs, $optionTag] = self::prepareAttrs($optionAttrs, 'option'); |
| 141 |
$option = Html::el(); |
| 142 |
$res = $tmp = ''; |
| 143 |
foreach ($items as $group => $subitems) { |
| 144 |
if (\is_array($subitems)) { |
| 145 |
$res .= Html::el('optgroup')->label($group)->startTag(); |
| 146 |
$tmp = '</optgroup>'; |
| 147 |
} else { |
| 148 |
$subitems = [$group => $subitems]; |
| 149 |
} |
| 150 |
foreach ($subitems as $value => $caption) { |
| 151 |
$option->value = $value; |
| 152 |
foreach ($optionAttrs as $k => $v) { |
| 153 |
$option->attrs[$k] = $v[$value] ?? null; |
| 154 |
} |
| 155 |
if ($caption instanceof Html) { |
| 156 |
$caption = clone $caption; |
| 157 |
$res .= $caption->setName('option')->addAttributes($option->attrs); |
| 158 |
} else { |
| 159 |
$res .= $optionTag . $option->attributes() . '>' . \htmlspecialchars((string) $caption, \ENT_NOQUOTES, 'UTF-8') . '</option>'; |
| 160 |
} |
| 161 |
if ($selected === $value) { |
| 162 |
unset($optionAttrs['selected'], $option->attrs['selected']); |
| 163 |
} |
| 164 |
} |
| 165 |
$res .= $tmp; |
| 166 |
$tmp = ''; |
| 167 |
} |
| 168 |
return Html::el('select')->setHtml($res); |
| 169 |
} |
| 170 |
private static function prepareAttrs(?array $attrs, string $name) : array |
| 171 |
{ |
| 172 |
$dynamic = []; |
| 173 |
foreach ((array) $attrs as $k => $v) { |
| 174 |
if ($k[-1] === '?' || $k[-1] === ':') { |
| 175 |
$p = \substr($k, 0, -1); |
| 176 |
unset($attrs[$k], $attrs[$p]); |
| 177 |
if ($k[-1] === '?') { |
| 178 |
$dynamic[$p] = \array_fill_keys((array) $v, \true); |
| 179 |
} elseif (\is_array($v) && $v) { |
| 180 |
$dynamic[$p] = $v; |
| 181 |
} else { |
| 182 |
$attrs[$p] = $v; |
| 183 |
} |
| 184 |
} |
| 185 |
} |
| 186 |
return [$dynamic, '<' . $name . Html::el(null, $attrs)->attributes()]; |
| 187 |
} |
| 188 |
/** @internal */ |
| 189 |
public static function iniGetSize(string $name) : int |
| 190 |
{ |
| 191 |
$value = \ini_get($name); |
| 192 |
$units = ['k' => 10, 'm' => 20, 'g' => 30]; |
| 193 |
return isset($units[$ch = \strtolower(\substr($value, -1))]) ? (int) $value << $units[$ch] : (int) $value; |
| 194 |
} |
| 195 |
} |
| 196 |
|