no message

This commit is contained in:
BarboraFleg
2019-04-04 18:34:40 +02:00
commit ce48ca4a21
589 changed files with 82184 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
{
"name": "nette-forms",
"description": "Client side script for Nette Forms Component",
"homepage": "https://nette.org",
"keywords": [
"nette",
"validation",
"forms"
],
"license": [
"BSD-3-Clause",
"GPL-2.0",
"GPL-3.0"
],
"authors": [
"David Grudl (https://davidgrudl.com)"
],
"main": "src/assets/netteForms.min.js",
"ignore": [
"src/Bridges",
"src/Forms",
"tests",
"examples",
"**/.*",
"*.json"
]
}
+41
View File
@@ -0,0 +1,41 @@
{
"name": "nette/forms",
"description": "?? Nette Forms: generating, validating and processing secure forms in PHP. Handy API, fully customizable, server & client side validation and mature design.",
"keywords": ["nette", "forms", "validation", "csrf", "javascript", "bootstrap"],
"homepage": "https://nette.org",
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"require": {
"php": ">=5.6.0",
"nette/component-model": "~2.3",
"nette/http": "^2.3.8",
"nette/utils": "^2.4.6"
},
"require-dev": {
"nette/di": "~2.4",
"nette/tester": "~2.0",
"latte/latte": "~2.4",
"tracy/tracy": "^2.4"
},
"conflict": {
"nette/nette": "<2.2"
},
"autoload": {
"classmap": ["src/"]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.4-dev"
}
}
}
+33
View File
@@ -0,0 +1,33 @@
How to contribute & use the issue tracker
=========================================
Nette welcomes your contributions. There are several ways to help out:
* Create an issue on GitHub, if you have found a bug
* Write test cases for open bug issues
* Write fixes for open bug/feature issues, preferably with test cases included
* Contribute to the [documentation](https://nette.org/en/writing)
Issues
------
Please **do not use the issue tracker to ask questions**. We will be happy to help you
on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette).
A good bug report shouldn't leave others needing to chase you up for more
information. Please try to be as detailed as possible in your report.
**Feature requests** are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. It's up to *you* to make a strong
case to convince the project's developers of the merits of this feature.
Contributing
------------
If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing).
The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them.
Please do not fix whitespace, format code, or make a purely cosmetic patch.
Thanks! :heart:
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

+72
View File
@@ -0,0 +1,72 @@
/** common style for Nette examples */
html {
font: 16px/1.5 sans-serif;
border-top: 4.7em solid #F4EBDB;
}
body {
max-width: 990px;
margin: -4.7em auto 0;
background: white;
color: #333;
}
h1 {
font-size: 1.9em;
margin: .5em 0 1.5em;
background: url(logo.png) right center no-repeat;
color: #7A7772;
text-shadow: 1px 1px 0 white;
}
fieldset {
padding: .2em 1em 1em;
margin: .5em 0;
background: #E4F1FC;
border: 1px solid #B2D1EB;
}
textarea,
select,
input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="image"]):not([type="range"]) {
padding: .3em .5em;
color: black;
background: white;
border: 1px solid silver;
}
.has-error textarea,
.has-error select,
.has-error input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="image"]):not([type="range"]) {
border-color: #E22;
}
select {
padding-right: .3em;
}
input[type="submit"] {
font-size: 120%;
}
th {
width: 10em;
text-align: right;
font-weight: normal;
}
.required label {
font-weight: bold;
}
.error {
color: #E22;
font-weight: bold;
margin-left: 1em;
}
footer a {
font-size: 70%;
color: gray;
}
+131
View File
@@ -0,0 +1,131 @@
<?php
/**
* Nette Forms basic example.
*/
if (@!include __DIR__ . '/../vendor/autoload.php') {
die('Install packages using `composer install`');
}
use Nette\Forms\Form;
use Nette\Utils\Html;
use Tracy\Debugger;
use Tracy\Dumper;
Debugger::enable();
$form = new Form;
// group Personal data
$form->addGroup('Personal data')
->setOption('description', 'We value your privacy and we ensure that the information you give to us will not be shared to other entities.');
$form->addText('name', 'Your name:')
->setRequired('Enter your name');
$form->addText('age', 'Your age:')
->setRequired('Enter your age')
->addRule($form::INTEGER, 'Age must be numeric value')
->addRule($form::RANGE, 'Age must be in range from %d to %d', [10, 100]);
$form->addRadioList('gender', 'Your gender:', [
'm' => 'male',
'f' => 'female',
]);
$form->addCheckboxList('colors', 'Favorite colors:', [
'r' => 'red',
'g' => 'green',
'b' => 'blue',
]);
$form->addEmail('email', 'Email:')
->setEmptyValue('@');
// group Shipping address
$form->addGroup('Shipping address')
->setOption('embedNext', true);
$form->addCheckbox('send', 'Ship to address')
->addCondition($form::FILLED) // conditional rule: if is checkbox checked...
->toggle('sendBox'); // toggle div #sendBox
// subgroup
$form->addGroup()
->setOption('container', Html::el('div')->id('sendBox'));
$form->addText('street', 'Street:');
$form->addText('city', 'City:')
->addConditionOn($form['send'], $form::FILLED)
->setRequired('Enter your shipping address');
$countries = [
'World' => [
'bu' => 'Buranda',
'qu' => 'Qumran',
'st' => 'Saint Georges Island',
],
'?' => 'other',
];
$form->addSelect('country', 'Country:', $countries)
->setPrompt('Select your country')
->addConditionOn($form['send'], $form::FILLED)
->setRequired('Select your country');
// group Your account
$form->addGroup('Your account');
$form->addPassword('password', 'Choose password:')
->setRequired('Choose your password')
->addRule($form::MIN_LENGTH, 'The password is too short: it must be at least %d characters', 3);
$form->addPassword('password2', 'Reenter password:')
->setRequired('Reenter your password')
->addRule($form::EQUAL, 'Passwords do not match', $form['password']);
$form->addUpload('avatar', 'Picture:')
->setRequired(false)
->addRule($form::IMAGE, 'Uploaded file is not image');
$form->addHidden('userid');
$form->addTextArea('note', 'Comment:');
// group for buttons
$form->addGroup();
$form->addSubmit('submit', 'Send');
$form->setDefaults([
'name' => 'John Doe',
'userid' => 231,
]);
if ($form->isSuccess()) {
echo '<h2>Form was submitted and successfully validated</h2>';
Dumper::dump($form->getValues(), [Dumper::COLLAPSE => false]);
exit;
}
?>
<!DOCTYPE html>
<meta charset="utf-8">
<title>Nette Forms basic example</title>
<link rel="stylesheet" media="screen" href="assets/style.css" />
<script src="https://nette.github.io/resources/js/netteForms.js"></script>
<h1>Nette Forms basic example</h1>
<?php echo $form ?>
<footer><a href="https://doc.nette.org/en/forms">see documentation</a></footer>
+95
View File
@@ -0,0 +1,95 @@
<?php
/**
* Nette Forms & Bootstap v2 rendering example.
*/
if (@!include __DIR__ . '/../vendor/autoload.php') {
die('Install packages using `composer install`');
}
use Nette\Forms\Form;
use Tracy\Debugger;
use Tracy\Dumper;
Debugger::enable();
function makeBootstrap2(Form $form)
{
$renderer = $form->getRenderer();
$renderer->wrappers['controls']['container'] = null;
$renderer->wrappers['pair']['container'] = 'div class=control-group';
$renderer->wrappers['pair']['.error'] = 'error';
$renderer->wrappers['control']['container'] = 'div class=controls';
$renderer->wrappers['label']['container'] = 'div class=control-label';
$renderer->wrappers['control']['description'] = 'span class=help-inline';
$renderer->wrappers['control']['errorcontainer'] = 'span class=help-inline';
$form->getElementPrototype()->class('form-horizontal');
foreach ($form->getControls() as $control) {
$type = $control->getOption('type');
if ($type === 'button') {
$control->getControlPrototype()->addClass(empty($usedPrimary) ? 'btn btn-primary' : 'btn');
$usedPrimary = true;
} elseif (in_array($type, ['checkbox', 'radio'], true)) {
$control->getSeparatorPrototype()->setName('div')->addClass($type);
}
}
}
$form = new Form;
$form->onRender[] = 'makeBootstrap2';
$form->addGroup('Personal data');
$form->addText('name', 'Your name')
->setRequired('Enter your name');
$form->addRadioList('gender', 'Your gender', [
'male', 'female',
]);
$form->addCheckboxList('colors', 'Favorite colors', [
'red', 'green', 'blue',
]);
$form->addSelect('country', 'Country', [
'Buranda', 'Qumran', 'Saint Georges Island',
]);
$form->addCheckbox('send', 'Ship to address');
$form->addGroup('Your account');
$form->addPassword('password', 'Choose password');
$form->addUpload('avatar', 'Picture');
$form->addTextArea('note', 'Comment');
$form->addGroup();
$form->addSubmit('submit', 'Send');
$form->addSubmit('cancel', 'Cancel');
if ($form->isSuccess()) {
echo '<h2>Form was submitted and successfully validated</h2>';
Dumper::dump($form->getValues());
exit;
}
?>
<!DOCTYPE html>
<meta charset="utf-8">
<title>Nette Forms & Bootstrap v2 rendering example</title>
<link rel="stylesheet" media="screen" href="https://netdna.bootstrapcdn.com/bootstrap/2.3.2/css/bootstrap.min.css" />
<div class="container">
<div class="page-header">
<h1>Nette Forms & Bootstrap v2 rendering example</h1>
</div>
<?php echo $form ?>
</div>
+98
View File
@@ -0,0 +1,98 @@
<?php
/**
* Nette Forms & Bootstap v3 rendering example.
*/
if (@!include __DIR__ . '/../vendor/autoload.php') {
die('Install packages using `composer install`');
}
use Nette\Forms\Form;
use Tracy\Debugger;
use Tracy\Dumper;
Debugger::enable();
function makeBootstrap3(Form $form)
{
$renderer = $form->getRenderer();
$renderer->wrappers['controls']['container'] = null;
$renderer->wrappers['pair']['container'] = 'div class=form-group';
$renderer->wrappers['pair']['.error'] = 'has-error';
$renderer->wrappers['control']['container'] = 'div class=col-sm-9';
$renderer->wrappers['label']['container'] = 'div class="col-sm-3 control-label"';
$renderer->wrappers['control']['description'] = 'span class=help-block';
$renderer->wrappers['control']['errorcontainer'] = 'span class=help-block';
$form->getElementPrototype()->class('form-horizontal');
foreach ($form->getControls() as $control) {
$type = $control->getOption('type');
if ($type === 'button') {
$control->getControlPrototype()->addClass(empty($usedPrimary) ? 'btn btn-primary' : 'btn btn-default');
$usedPrimary = true;
} elseif (in_array($type, ['text', 'textarea', 'select'], true)) {
$control->getControlPrototype()->addClass('form-control');
} elseif (in_array($type, ['checkbox', 'radio'], true)) {
$control->getSeparatorPrototype()->setName('div')->addClass($type);
}
}
}
$form = new Form;
$form->onRender[] = 'makeBootstrap3';
$form->addGroup('Personal data');
$form->addText('name', 'Your name')
->setRequired('Enter your name');
$form->addRadioList('gender', 'Your gender', [
'male', 'female',
]);
$form->addCheckboxList('colors', 'Favorite colors', [
'red', 'green', 'blue',
]);
$form->addSelect('country', 'Country', [
'Buranda', 'Qumran', 'Saint Georges Island',
]);
$form->addCheckbox('send', 'Ship to address');
$form->addGroup('Your account');
$form->addPassword('password', 'Choose password');
$form->addUpload('avatar', 'Picture');
$form->addTextArea('note', 'Comment');
$form->addGroup();
$form->addSubmit('submit', 'Send');
$form->addSubmit('cancel', 'Cancel');
if ($form->isSuccess()) {
echo '<h2>Form was submitted and successfully validated</h2>';
Dumper::dump($form->getValues());
exit;
}
?>
<!DOCTYPE html>
<meta charset="utf-8">
<title>Nette Forms & Bootstrap v3 rendering example</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<div class="container">
<div class="page-header">
<h1>Nette Forms & Bootstrap v3 rendering example</h1>
</div>
<?php echo $form ?>
</div>
+104
View File
@@ -0,0 +1,104 @@
<?php
/**
* Nette Forms & Bootstap v4 rendering example.
*/
if (@!include __DIR__ . '/../vendor/autoload.php') {
die('Install packages using `composer install`');
}
use Nette\Forms\Form;
use Tracy\Debugger;
use Tracy\Dumper;
Debugger::enable();
function makeBootstrap4(Form $form)
{
$renderer = $form->getRenderer();
$renderer->wrappers['controls']['container'] = null;
$renderer->wrappers['pair']['container'] = 'div class="form-group row"';
$renderer->wrappers['pair']['.error'] = 'has-danger';
$renderer->wrappers['control']['container'] = 'div class=col-sm-9';
$renderer->wrappers['label']['container'] = 'div class="col-sm-3 col-form-label"';
$renderer->wrappers['control']['description'] = 'span class=form-text';
$renderer->wrappers['control']['errorcontainer'] = 'span class=form-control-feedback';
foreach ($form->getControls() as $control) {
$type = $control->getOption('type');
if ($type === 'button') {
$control->getControlPrototype()->addClass(empty($usedPrimary) ? 'btn btn-primary' : 'btn btn-secondary');
$usedPrimary = true;
} elseif (in_array($type, ['text', 'textarea', 'select'], true)) {
$control->getControlPrototype()->addClass('form-control');
} elseif ($type === 'file') {
$control->getControlPrototype()->addClass('form-control-file');
} elseif (in_array($type, ['checkbox', 'radio'], true)) {
if ($control instanceof Nette\Forms\Controls\Checkbox) {
$control->getLabelPrototype()->addClass('form-check-label');
} else {
$control->getItemLabelPrototype()->addClass('form-check-label');
}
$control->getControlPrototype()->addClass('form-check-input');
$control->getSeparatorPrototype()->setName('div')->addClass('form-check');
}
}
}
$form = new Form;
$form->onRender[] = 'makeBootstrap4';
$form->addGroup('Personal data');
$form->addText('name', 'Your name')
->setRequired('Enter your name');
$form->addRadioList('gender', 'Your gender', [
'male', 'female',
]);
$form->addCheckboxList('colors', 'Favorite colors', [
'red', 'green', 'blue',
]);
$form->addSelect('country', 'Country', [
'Buranda', 'Qumran', 'Saint Georges Island',
]);
$form->addCheckbox('send', 'Ship to address');
$form->addGroup('Your account');
$form->addPassword('password', 'Choose password');
$form->addUpload('avatar', 'Picture');
$form->addTextArea('note', 'Comment');
$form->addGroup();
$form->addSubmit('submit', 'Send');
$form->addSubmit('cancel', 'Cancel');
if ($form->isSuccess()) {
echo '<h2>Form was submitted and successfully validated</h2>';
Dumper::dump($form->getValues());
exit;
}
?>
<!DOCTYPE html>
<meta charset="utf-8">
<title>Nette Forms & Bootstrap v4 rendering example</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<div class="container">
<h1>Nette Forms & Bootstrap v4 rendering example</h1>
<?php echo $form ?>
</div>
+62
View File
@@ -0,0 +1,62 @@
<?php
/**
* Nette Forms containers example.
*/
if (@!include __DIR__ . '/../vendor/autoload.php') {
die('Install packages using `composer install`');
}
use Nette\Forms\Form;
use Tracy\Debugger;
use Tracy\Dumper;
Debugger::enable();
$form = new Form;
// group First person
$form->addGroup('First person');
$first = $form->addContainer('first');
$first->addText('name', 'Your name:');
$first->addText('email', 'Email:');
$first->addText('street', 'Street:');
$first->addText('city', 'City:');
// group Second person
$form->addGroup('Second person');
$second = $form->addContainer('second');
$second->addText('name', 'Your name:');
$second->addText('email', 'Email:');
$second->addText('street', 'Street:');
$second->addText('city', 'City:');
// group for button
$form->addGroup();
$form->addSubmit('submit', 'Send');
if ($form->isSuccess()) {
echo '<h2>Form was submitted and successfully validated</h2>';
Dumper::dump($form->getValues());
exit;
}
?>
<!DOCTYPE html>
<meta charset="utf-8">
<title>Nette Forms containers example</title>
<link rel="stylesheet" media="screen" href="assets/style.css" />
<h1>Nette Forms containers example</h1>
<?php echo $form ?>
<footer><a href="https://doc.nette.org/en/forms">see documentation</a></footer>
+148
View File
@@ -0,0 +1,148 @@
<?php
/**
* Nette Forms custom control example.
*/
if (@!include __DIR__ . '/../vendor/autoload.php') {
die('Install packages using `composer install`');
}
use Nette\Forms\Form;
use Nette\Forms\Helpers;
use Nette\Utils\Html;
class DateInput extends Nette\Forms\Controls\BaseControl
{
/** @var string */
private $day = '';
private $month = '';
private $year = '';
public function __construct($label = null)
{
parent::__construct($label);
$this->setRequired(false)
->addRule([__CLASS__, 'validateDate'], 'Date is invalid.');
}
public function setValue($value)
{
if ($value === null) {
$this->day = $this->month = $this->year = '';
} else {
$date = Nette\Utils\DateTime::from($value);
$this->day = $date->format('j');
$this->month = $date->format('n');
$this->year = $date->format('Y');
}
return $this;
}
/**
* @return DateTimeImmutable|null
*/
public function getValue()
{
return self::validateDate($this)
? (new DateTimeImmutable)->setDate((int) $this->year, (int) $this->month, (int) $this->day)->setTime(0, 0)
: null;
}
/**
* @return bool
*/
public function isFilled()
{
return $this->day !== '' || $this->year !== '';
}
public function loadHttpData()
{
$this->day = $this->getHttpData(Form::DATA_LINE, '[day]');
$this->month = $this->getHttpData(Form::DATA_LINE, '[month]');
$this->year = $this->getHttpData(Form::DATA_LINE, '[year]');
}
/**
* Generates control's HTML element.
*/
public function getControl()
{
$name = $this->getHtmlName();
return Html::el('input', [
'name' => $name . '[day]',
'id' => $this->getHtmlId(),
'value' => $this->day,
'type' => 'number',
'min' => 1,
'max' => 31,
'data-nette-rules' => Helpers::exportRules($this->getRules()) ?: null,
])
. Helpers::createSelectBox(
[1 => 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
[],
$this->month
)->name($name . '[month]')
. Html::el('input', [
'name' => $name . '[year]',
'value' => $this->year,
'type' => 'number',
]);
}
/**
* @return bool
*/
public static function validateDate(Nette\Forms\IControl $control)
{
return ctype_digit($control->day)
&& ctype_digit($control->month)
&& ctype_digit($control->year)
&& checkdate((int) $control->month, (int) $control->day, (int) $control->year);
}
}
Tracy\Debugger::enable();
$form = new Form;
$form['date'] = new DateInput('Date:');
$form['date']->setDefaultValue(new DateTime);
$form->addSubmit('submit', 'Send');
if ($form->isSuccess()) {
echo '<h2>Form was submitted and successfully validated</h2>';
Tracy\Dumper::dump($form->getValues());
exit;
}
?>
<!DOCTYPE html>
<meta charset="utf-8">
<title>Nette Forms custom control example</title>
<link rel="stylesheet" media="screen" href="assets/style.css" />
<script src="https://nette.github.io/resources/js/netteForms.js"></script>
<h1>Nette Forms custom control example</h1>
<?php echo $form ?>
<footer><a href="https://doc.nette.org/en/forms">see documentation</a></footer>
+125
View File
@@ -0,0 +1,125 @@
<?php
/**
* Nette Forms custom rendering example.
*/
if (@!include __DIR__ . '/../vendor/autoload.php') {
die('Install packages using `composer install`');
}
use Nette\Forms\Form;
use Nette\Utils\Html;
use Tracy\Debugger;
use Tracy\Dumper;
Debugger::enable();
$form = new Form;
// setup custom rendering
$renderer = $form->getRenderer();
$renderer->wrappers['form']['container'] = Html::el('div')->id('form');
$renderer->wrappers['group']['container'] = null;
$renderer->wrappers['group']['label'] = 'h3';
$renderer->wrappers['pair']['container'] = null;
$renderer->wrappers['controls']['container'] = 'dl';
$renderer->wrappers['control']['container'] = 'dd';
$renderer->wrappers['control']['.odd'] = 'odd';
$renderer->wrappers['label']['container'] = 'dt';
$renderer->wrappers['label']['suffix'] = ':';
$renderer->wrappers['control']['requiredsuffix'] = " \xE2\x80\xA2";
$form->addGroup('Personal data');
$form->addText('name', 'Your name')
->setRequired('Enter your name');
$form->addRadioList('gender', 'Your gender', [
'm' => Html::el('span', 'male')->style('color: #248bd3'),
'f' => Html::el('span', 'female')->style('color: #e948d4'),
]);
$form->addSelect('country', 'Country', [
'Buranda', 'Qumran', 'Saint Georges Island',
]);
$form->addCheckbox('send', 'Ship to address');
$form->addGroup('Your account');
$form->addPassword('password', 'Choose password');
$form->addUpload('avatar', 'Picture');
$form->addTextArea('note', 'Comment');
$form->addGroup();
$form->addSubmit('submit', 'Send');
if ($form->isSuccess()) {
echo '<h2>Form was submitted and successfully validated</h2>';
Dumper::dump($form->getValues());
exit;
}
?>
<!DOCTYPE html>
<meta charset="utf-8">
<title>Nette Forms custom rendering example</title>
<link rel="stylesheet" media="screen" href="assets/style.css" />
<style>
textarea, select, input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="image"]):not([type="range"]) {
border: 1px solid #78BD3F;
}
dt, dd {
padding: .5em 1em;
}
#form h3 {
background: #78BD3F;
color: white;
margin: 0;
padding: .1em 1em;
font-size: 100%;
font-weight: normal;
clear: both;
}
#form dl {
background: #F8F8F8;
margin: 0;
}
#form dt {
text-align: right;
font-weight: normal;
float: left;
width: 10em;
clear: both;
}
#form dd {
margin: 0;
padding-left: 10em;
display: block;
}
#form dd ul {
list-style: none;
font-size: 90%;
}
#form dd.odd {
background: #EEE;
}
</style>
<script src="https://nette.github.io/resources/js/netteForms.js"></script>
<h1>Nette Forms custom rendering example</h1>
<?php echo $form ?>
<footer><a href="https://doc.nette.org/en/forms">see documentation</a></footer>
+62
View File
@@ -0,0 +1,62 @@
<?php
/**
* Nette Forms custom validator example.
*/
if (@!include __DIR__ . '/../vendor/autoload.php') {
die('Install packages using `composer install`');
}
use Nette\Forms\Form;
use Tracy\Debugger;
use Tracy\Dumper;
Debugger::enable();
// Define custom validator
class MyValidators
{
public static function divisibilityValidator($item, $arg)
{
return $item->value % $arg === 0;
}
}
$form = new Form;
$form->addText('num1', 'Multiple of 8:')
->setDefaultValue(5)
->addRule('MyValidators::divisibilityValidator', 'First number must be %d multiple', 8);
$form->addSubmit('submit', 'Send');
if ($form->isSuccess()) {
echo '<h2>Form was submitted and successfully validated</h2>';
Dumper::dump($form->getValues());
exit;
}
?>
<!DOCTYPE html>
<meta charset="utf-8">
<title>Nette Forms custom validator example</title>
<link rel="stylesheet" media="screen" href="assets/style.css" />
<script src="https://nette.github.io/resources/js/netteForms.js"></script>
<script>
Nette.validators.MyValidators_divisibilityValidator = function(elem, args, val) {
return val % args === 0;
};
</script>
<h1>Nette Forms custom validator example</h1>
<?php echo $form ?>
<footer><a href="https://doc.nette.org/en/forms">see documentation</a></footer>
+64
View File
@@ -0,0 +1,64 @@
<?php
/**
* Nette Forms and HTML5.
*/
if (@!include __DIR__ . '/../vendor/autoload.php') {
die('Install packages using `composer install`');
}
use Nette\Forms\Form;
use Tracy\Debugger;
use Tracy\Dumper;
Debugger::enable();
$form = new Form;
$form->addGroup();
$form->addText('query', 'Search:')
->setHtmlType('search')
->setHtmlAttribute('autofocus');
$form->addText('count', 'Number of results:')
->setHtmlType('number')
->setDefaultValue(10)
->addRule($form::INTEGER, 'Must be numeric value')
->addRule($form::RANGE, 'Must be in range from %d to %d', [1, 100]);
$form->addText('precision', 'Precision:')
->setHtmlType('range')
->setDefaultValue(50)
->addRule($form::INTEGER, 'Precision must be numeric value')
->addRule($form::RANGE, 'Precision must be in range from %d to %d', [0, 100]);
$form->addEmail('email', 'Send to email:')
->setHtmlAttribute('autocomplete', 'off')
->setHtmlAttribute('placeholder', 'Optional, but Recommended');
$form->addSubmit('submit', 'Send');
if ($form->isSuccess()) {
echo '<h2>Form was submitted and successfully validated</h2>';
Dumper::dump($form->getValues());
exit;
}
?>
<!DOCTYPE html>
<meta charset="utf-8">
<title>Nette Forms and HTML5</title>
<link rel="stylesheet" media="screen" href="assets/style.css" />
<script src="https://nette.github.io/resources/js/netteForms.js"></script>
<h1>Nette Forms and HTML5</h1>
<?php echo $form ?>
<footer><a href="https://doc.nette.org/en/forms">see documentation</a></footer>
+104
View File
@@ -0,0 +1,104 @@
<?php
/**
* Nette Forms live validation example.
*/
if (@!include __DIR__ . '/../vendor/autoload.php') {
die('Install packages using `composer install`');
}
use Nette\Forms\Form;
use Tracy\Debugger;
use Tracy\Dumper;
Debugger::enable();
$form = new Form;
$form->addText('name', 'Your name:')
->setRequired('Enter your name');
$form->addText('age', 'Your age:')
->setRequired('Enter your age')
->addRule($form::INTEGER, 'Age must be numeric value')
->addRule($form::RANGE, 'Age must be in range from %d to %d', [10, 100]);
$form->addPassword('password', 'Choose password:')
->setRequired('Choose your password')
->addRule($form::MIN_LENGTH, 'The password is too short: it must be at least %d characters', 3);
$form->addPassword('password2', 'Reenter password:')
->setRequired('Reenter your password')
->addRule($form::EQUAL, 'Passwords do not match', $form['password']);
$form->addSubmit('submit', 'Send');
if ($form->isSuccess()) {
echo '<h2>Form was submitted and successfully validated</h2>';
Dumper::dump($form->getValues());
exit;
}
$renderer = $form->getRenderer();
$renderer->wrappers['pair']['.error'] = 'has-error';
?>
<!DOCTYPE html>
<meta charset="utf-8">
<title>Nette Forms live validation example</title>
<link rel="stylesheet" media="screen" href="assets/style.css" />
<script src="https://nette.github.io/resources/js/netteForms.js"></script>
<script src="https://code.jquery.com/jquery-3.0.0.min.js" integrity="sha256-JmvOoLtYsmqlsWxa7mDSLMwa6dZ9rrIdtrrVYRnDRH0=" crossorigin="anonymous"></script>
<script>
function showErrors(errors, focus)
{
errors.forEach(function(error) {
if (error.message) {
$(error.element).closest('tr').addClass('has-error').find('.error').remove();
$('<span class=error>').text(error.message).insertAfter(error.element);
}
if (focus && error.element.focus) {
error.element.focus();
focus = false;
}
});
}
function removeErrors(elem)
{
if ($(elem).is('form')) {
$('.has-error', elem).removeClass('has-error');
$('.error', elem).remove();
} else {
$(elem).closest('tr').removeClass('has-error').find('.error').remove();
}
}
Nette.showFormErrors = function(form, errors) {
removeErrors(form);
showErrors(errors, true);
};
$(function() {
$(':input').keypress(function() {
removeErrors(this);
});
$(':input').blur(function() {
Nette.formErrors = [];
Nette.validateControl(this);
showErrors(Nette.formErrors);
});
});
</script>
<h1>Nette Forms live validation example</h1>
<?php echo $form ?>
<footer><a href="https://doc.nette.org/en/forms">see documentation</a></footer>
+12
View File
@@ -0,0 +1,12 @@
Personal data = Osobní údaje
Your name: = Jméno:
Enter your name = Zadejte jméno
Your age: = Věk:
Enter your age = Zadejte váš věk
Age must be numeric value = Věk musí být číslo
Age must be in range from %d to %d = Věk musí být v rozmezí %d až %d
Country: = Země:
Select your country = Vyberte zemi
World = Svět
other = jiná
Send = Odeslat
+87
View File
@@ -0,0 +1,87 @@
<?php
/**
* Nette Forms localization example.
*/
if (@!include __DIR__ . '/../vendor/autoload.php') {
die('Install packages using `composer install`');
}
use Nette\Forms\Form;
use Tracy\Debugger;
use Tracy\Dumper;
Debugger::enable();
class MyTranslator implements Nette\Localization\ITranslator
{
private $table;
public function __construct(array $table)
{
$this->table = $table;
}
/**
* Translates the given string.
*/
public function translate($message, $count = null)
{
return isset($this->table[$message]) ? $this->table[$message] : $message;
}
}
$form = new Form;
$translator = new MyTranslator(parse_ini_file(__DIR__ . '/localization.ini'));
$form->setTranslator($translator);
$form->addGroup('Personal data');
$form->addText('name', 'Your name:')
->setRequired('Enter your name');
$form->addText('age', 'Your age:')
->setRequired('Enter your age')
->addRule($form::INTEGER, 'Age must be numeric value')
->addRule($form::RANGE, 'Age must be in range from %d to %d', [10, 100]);
$countries = [
'World' => [
'bu' => 'Buranda',
'qu' => 'Qumran',
'st' => 'Saint Georges Island',
],
'?' => 'other',
];
$form->addSelect('country', 'Country:', $countries)
->setPrompt('Select your country');
$form->addSubmit('submit', 'Send');
if ($form->isSuccess()) {
echo '<h2>Form was submitted and successfully validated</h2>';
Dumper::dump($form->getValues());
exit;
}
?>
<!DOCTYPE html>
<meta charset="utf-8">
<title>Nette Forms localization example</title>
<link rel="stylesheet" media="screen" href="assets/style.css" />
<script src="https://nette.github.io/resources/js/netteForms.js"></script>
<h1>Nette Forms localization example</h1>
<?php echo $form ?>
<footer><a href="https://doc.nette.org/en/forms">see documentation</a></footer>
+95
View File
@@ -0,0 +1,95 @@
<?php
/**
* Nette Forms manual form rendering.
*/
if (@!include __DIR__ . '/../vendor/autoload.php') {
die('Install packages using `composer install`');
}
use Nette\Forms\Form;
use Tracy\Debugger;
use Tracy\Dumper;
Debugger::enable();
$form = new Form;
$form->addText('name')
->setRequired('Enter your name');
$form->addText('age')
->setRequired('Enter your age');
$form->addRadioList('gender', null, [
'm' => 'male',
'f' => 'female',
]);
$form->addText('email')
->setRequired(false)
->addRule($form::EMAIL, 'Incorrect email address');
$form->addSubmit('submit');
if ($form->isSuccess()) {
echo '<h2>Form was submitted and successfully validated</h2>';
Dumper::dump($form->getValues());
exit;
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Nette Forms manual form rendering</title>
<link rel="stylesheet" media="screen" href="assets/style.css" />
<script src="https://nette.github.io/resources/js/netteForms.js"></script>
</head>
<body>
<h1>Nette Forms manual form rendering</h1>
<?php $form->render('begin') ?>
<?php if ($form->errors): ?>
<ul class="error">
<?php foreach ($form->errors as $error): ?>
<li><?php echo htmlspecialchars($error) ?></li>
<?php endforeach ?>
</ul>
<?php endif ?>
<fieldset>
<legend>Personal data</legend>
<table>
<tr class="required">
<th><?php echo $form['name']->getLabel('Your name:') ?></th>
<td><?php echo $form['name']->control->cols(35) ?> <?php echo $form['name']->error ?></td>
</tr>
<tr class="required">
<th><?php echo $form['age']->getLabel('Your age:') ?></th>
<td><?php echo $form['age']->control->cols(5) ?> <?php echo $form['age']->error ?></td>
</tr>
<tr>
<th><?php echo $form['gender']->getLabel('Your gender:') ?></th>
<td><?php echo $form['gender']->control ?> <?php echo $form['gender']->error ?></td>
</tr>
<tr>
<th><?php echo $form['email']->getLabel('Email:') ?></th>
<td><?php echo $form['email']->control->cols(35) ?> <?php echo $form['email']->error ?></td>
</tr>
</table>
</fieldset>
<div>
<?php echo $form['submit']->getControl('Send') ?>
</div>
<?php $form->render('end'); ?>
</body>
</html>
+60
View File
@@ -0,0 +1,60 @@
Licenses
========
Good news! You may use Nette Framework under the terms of either
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
The BSD License is recommended for most projects. It is easy to understand and it
places almost no restrictions on what you can do with the framework. If the GPL
fits better to your project, you can use the framework under this license.
You don't have to notify anyone which license you are using. You can freely
use Nette Framework in commercial projects as long as the copyright header
remains intact.
Please be advised that the name "Nette Framework" is a protected trademark and its
usage has some limitations. So please do not use word "Nette" in the name of your
project or top-level domain, and choose a name that stands on its own merits.
If your stuff is good, it will not take long to establish a reputation for yourselves.
New BSD License
---------------
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of "Nette Framework" nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
This software is provided by the copyright holders and contributors "as is" and
any express or implied warranties, including, but not limited to, the implied
warranties of merchantability and fitness for a particular purpose are
disclaimed. In no event shall the copyright owner or contributors be liable for
any direct, indirect, incidental, special, exemplary, or consequential damages
(including, but not limited to, procurement of substitute goods or services;
loss of use, data, or profits; or business interruption) however caused and on
any theory of liability, whether in contract, strict liability, or tort
(including negligence or otherwise) arising in any way out of the use of this
software, even if advised of the possibility of such damage.
GNU General Public License
--------------------------
GPL licenses are very very long, so instead of including them here we offer
you URLs with full text:
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)
+21
View File
@@ -0,0 +1,21 @@
{
"name": "nette-forms",
"version": "2.4.9",
"description": "Client side script for Nette Forms Component",
"keywords": [
"nette",
"validation",
"forms"
],
"homepage": "https://nette.org",
"author": "David Grudl (https://davidgrudl.com)",
"license": "BSD-3-Clause",
"main": "src/assets/netteForms.js",
"files": [
"src/assets"
],
"repository": {
"type": "git",
"url": "git+https://github.com/nette/forms.git"
}
}
+69
View File
@@ -0,0 +1,69 @@
Nette Forms: greatly facilitates web forms
==========================================
[![Downloads this Month](https://img.shields.io/packagist/dm/nette/forms.svg)](https://packagist.org/packages/nette/forms)
[![Build Status](https://travis-ci.org/nette/forms.svg?branch=master)](https://travis-ci.org/nette/forms)
[![Coverage Status](https://coveralls.io/repos/github/nette/forms/badge.svg?branch=master)](https://coveralls.io/github/nette/forms?branch=master)
[![Latest Stable Version](https://poser.pugx.org/nette/forms/v/stable)](https://github.com/nette/forms/releases)
[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/forms/blob/master/license.md)
Nette\Forms greatly facilitates creating and processing web forms. What it can really do?
- validate sent data both client-side (JavaScript) and server-side
- provide high level of security
- multiple render modes
- translations, i18n
Why should you bother setting up framework for a simple web form? You won't have to take care about routine tasks such as writing two validation scripts (client and server) and your code will be safe against security breaches.
Nette Framework puts a great effort to be safe and since forms are the most common user input, Nette forms are as good as impenetrable. All is maintained dynamically and transparently, nothing has to be set manually. Well-known vulnerabilities such as Cross Site Scripting (XSS) and Cross-Site Request Forgery (CSRF) are filtered, as well as special control characters. All inputs are checked for UTF-8 validity. Every multiple-choice, select boxes and similar are checked for forged values upon validating. Sounds good? Let's try it out.
Documentation
-------------
This is just a piece of documentation. [Please see our website](https://doc.nette.org/forms).
First form
----------
Let's create a simple registration form:
```php
use Nette\Forms\Form;
$form = new Form;
$form->addText('name', 'Name:');
$form->addPassword('password', 'Password:');
$form->addSubmit('send', 'Register');
echo $form; // renders the form
```
Although we mentioned validation, our form has none. Let's fix it. In order to require user's name, call `setRequired()` method on the form item. You can pass an error message as optional argument and it will be displayed if user does not fill his name in:
```php
$form->addText('name', 'Name:')
->setRequired('Please fill your name.');
```
Try submitting a form without the name - the message is displayed unless you meet the validation rules.
The form is validated on both the client and server side. You only need to link `netteForms.js`, which is located at `/src/assets` in the distribution package.
```html
<script src="netteForms.js"></script>
```
Nette Framework adds `required` class to all mandatory elements. Adding the following style will turn label of *name* input to red.
```html
<style>
.required label { color: maroon }
</style>
```
[Continue…](https://doc.nette.org/forms).
@@ -0,0 +1,38 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\FormsDI;
use Nette;
/**
* Forms extension for Nette DI.
*/
class FormsExtension extends Nette\DI\CompilerExtension
{
public $defaults = [
'messages' => [],
];
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
$initialize = $class->getMethod('initialize');
$config = $this->validateConfig($this->defaults);
foreach ((array) $config['messages'] as $name => $text) {
if (defined('Nette\Forms\Form::' . $name)) {
$initialize->addBody('Nette\Forms\Validator::$messages[Nette\Forms\Form::?] = ?;', [$name, $text]);
} elseif (defined($name)) {
$initialize->addBody('Nette\Forms\Validator::$messages[' . $name . '] = ?;', [$text]);
} else {
throw new Nette\InvalidArgumentException('Constant Nette\Forms\Form::' . $name . ' or constant ' . $name . ' does not exist.');
}
}
}
}
+257
View File
@@ -0,0 +1,257 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\FormsLatte;
use Latte;
use Latte\CompileException;
use Latte\MacroNode;
use Latte\Macros\MacroSet;
use Latte\PhpWriter;
/**
* Latte macros for Nette\Forms.
*
* - {form name} ... {/form}
* - {input name}
* - {label name /} or {label name}... {/label}
* - {inputError name}
* - {formContainer name} ... {/formContainer}
*/
class FormMacros extends MacroSet
{
public static function install(Latte\Compiler $compiler)
{
$me = new static($compiler);
$me->addMacro('form', [$me, 'macroForm'], 'echo Nette\Bridges\FormsLatte\Runtime::renderFormEnd(array_pop($this->global->formsStack));');
$me->addMacro('formContainer', [$me, 'macroFormContainer'], 'array_pop($this->global->formsStack); $formContainer = $_form = end($this->global->formsStack)');
$me->addMacro('label', [$me, 'macroLabel'], [$me, 'macroLabelEnd'], null, self::AUTO_EMPTY);
$me->addMacro('input', [$me, 'macroInput']);
$me->addMacro('name', [$me, 'macroName'], [$me, 'macroNameEnd'], [$me, 'macroNameAttr']);
$me->addMacro('inputError', [$me, 'macroInputError']);
}
/********************* macros ****************d*g**/
/**
* {form ...}
*/
public function macroForm(MacroNode $node, PhpWriter $writer)
{
if ($node->modifiers) {
throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
}
if ($node->prefix) {
throw new CompileException('Did you mean <form n:name=...> ?');
}
$name = $node->tokenizer->fetchWord();
if ($name === false) {
throw new CompileException('Missing form name in ' . $node->getNotation());
}
$node->replaced = true;
$node->tokenizer->reset();
return $writer->write(
"/* line $node->startLine */\n"
. 'echo Nette\Bridges\FormsLatte\Runtime::renderFormBegin($form = $_form = $this->global->formsStack[] = '
. ($name[0] === '$' ? 'is_object(%node.word) ? %node.word : ' : '')
. '$this->global->uiControl[%node.word], %node.array);'
);
}
/**
* {formContainer ...}
*/
public function macroFormContainer(MacroNode $node, PhpWriter $writer)
{
if ($node->modifiers) {
throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
}
$name = $node->tokenizer->fetchWord();
if ($name === false) {
throw new CompileException('Missing name in ' . $node->getNotation());
}
$node->tokenizer->reset();
return $writer->write(
'$this->global->formsStack[] = $formContainer = $_form = '
. ($name[0] === '$' ? 'is_object(%node.word) ? %node.word : ' : '')
. 'end($this->global->formsStack)[%node.word];'
);
}
/**
* {label ...}
*/
public function macroLabel(MacroNode $node, PhpWriter $writer)
{
if ($node->modifiers) {
throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
}
$words = $node->tokenizer->fetchWords();
if (!$words) {
throw new CompileException('Missing name in ' . $node->getNotation());
}
$node->replaced = true;
$name = array_shift($words);
return $writer->write(
($name[0] === '$' ? '$_input = is_object(%0.word) ? %0.word : end($this->global->formsStack)[%0.word]; if ($_label = $_input' : 'if ($_label = end($this->global->formsStack)[%0.word]')
. '->%1.raw) echo $_label'
. ($node->tokenizer->isNext() ? '->addAttributes(%node.array)' : ''),
$name,
$words ? ('getLabelPart(' . implode(', ', array_map([$writer, 'formatWord'], $words)) . ')') : 'getLabel()'
);
}
/**
* {/label}
*/
public function macroLabelEnd(MacroNode $node, PhpWriter $writer)
{
if ($node->content != null) {
$node->openingCode = rtrim($node->openingCode, '?> ') . '->startTag() ?>';
return $writer->write('if ($_label) echo $_label->endTag()');
}
}
/**
* {input ...}
*/
public function macroInput(MacroNode $node, PhpWriter $writer)
{
if ($node->modifiers) {
throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
}
$words = $node->tokenizer->fetchWords();
if (!$words) {
throw new CompileException('Missing name in ' . $node->getNotation());
}
$node->replaced = true;
$name = array_shift($words);
return $writer->write(
($name[0] === '$' ? '$_input = is_object(%0.word) ? %0.word : end($this->global->formsStack)[%0.word]; echo $_input' : 'echo end($this->global->formsStack)[%0.word]')
. '->%1.raw'
. ($node->tokenizer->isNext() ? '->addAttributes(%node.array)' : '')
. " /* line $node->startLine */",
$name,
$words ? 'getControlPart(' . implode(', ', array_map([$writer, 'formatWord'], $words)) . ')' : 'getControl()'
);
}
/**
* <form n:name>, <input n:name>, <select n:name>, <textarea n:name>, <label n:name> and <button n:name>
*/
public function macroNameAttr(MacroNode $node, PhpWriter $writer)
{
$words = $node->tokenizer->fetchWords();
if (!$words) {
throw new CompileException('Missing name in ' . $node->getNotation());
}
$name = array_shift($words);
$tagName = strtolower($node->htmlNode->name);
$node->empty = $tagName === 'input';
$definedHtmlAttributes = array_keys($node->htmlNode->attrs);
if (isset($node->htmlNode->macroAttrs['class'])) {
$definedHtmlAttributes[] = 'class';
}
if ($tagName === 'form') {
$node->openingCode = $writer->write(
'<?php $form = $_form = $this->global->formsStack[] = '
. ($name[0] === '$' ? 'is_object(%0.word) ? %0.word : ' : '')
. '$this->global->uiControl[%0.word]; ?>',
$name
);
return $writer->write(
'echo Nette\Bridges\FormsLatte\Runtime::renderFormBegin(end($this->global->formsStack), %0.var, false)',
array_fill_keys($definedHtmlAttributes, null)
);
} else {
$method = $tagName === 'label' ? 'getLabel' : 'getControl';
return $writer->write(
'$_input = ' . ($name[0] === '$' ? 'is_object(%0.word) ? %0.word : ' : '')
. 'end($this->global->formsStack)[%0.word]; echo $_input->%1.raw'
. ($definedHtmlAttributes ? '->addAttributes(%2.var)' : '') . '->attributes()',
$name,
$method . 'Part(' . implode(', ', array_map([$writer, 'formatWord'], $words)) . ')',
array_fill_keys($definedHtmlAttributes, null)
);
}
}
public function macroName(MacroNode $node, PhpWriter $writer)
{
if (!$node->prefix) {
throw new CompileException("Unknown macro {{$node->name}}, use n:{$node->name} attribute.");
} elseif ($node->prefix !== MacroNode::PREFIX_NONE) {
throw new CompileException("Unknown attribute n:{$node->prefix}-{$node->name}, use n:{$node->name} attribute.");
}
}
public function macroNameEnd(MacroNode $node, PhpWriter $writer)
{
$tagName = strtolower($node->htmlNode->name);
if ($tagName === 'form') {
$node->innerContent .= '<?php echo Nette\Bridges\FormsLatte\Runtime::renderFormEnd(array_pop($this->global->formsStack), false); ?>';
} elseif ($tagName === 'label') {
if ($node->htmlNode->empty) {
$node->innerContent = '<?php echo $_input->getLabelPart()->getHtml() ?>';
}
} elseif ($tagName === 'button') {
if ($node->htmlNode->empty) {
$node->innerContent = '<?php echo htmlspecialchars($_input->caption) ?>';
}
} else { // select, textarea
$node->innerContent = '<?php echo $_input->getControl()->getHtml() ?>';
}
}
/**
* {inputError ...}
*/
public function macroInputError(MacroNode $node, PhpWriter $writer)
{
if ($node->modifiers) {
throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
}
$name = $node->tokenizer->fetchWord();
$node->replaced = true;
if (!$name) {
return $writer->write('echo %escape($_input->getError());');
} elseif ($name[0] === '$') {
return $writer->write('$_input = is_object(%0.word) ? %0.word : end($this->global->formsStack)[%0.word]; echo %escape($_input->getError());', $name);
} else {
return $writer->write('echo %escape(end($this->global->formsStack)[%0.word]->getError());', $name);
}
}
/** @deprecated */
public static function renderFormBegin(Form $form, array $attrs, $withTags = true)
{
trigger_error(__METHOD__ . '() is deprecated, use Nette\Bridges\FormsLatte\Runtime::renderFormBegin()', E_USER_DEPRECATED);
echo Runtime::renderFormBegin($form, $attrs, $withTags);
}
/** @deprecated */
public static function renderFormEnd(Form $form, $withTags = true)
{
trigger_error(__METHOD__ . '() is deprecated, use Nette\Bridges\FormsLatte\Runtime::renderFormEnd()', E_USER_DEPRECATED);
echo Runtime::renderFormEnd($form, $withTags);
}
}
+74
View File
@@ -0,0 +1,74 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\FormsLatte;
use Nette;
use Nette\Forms\Form;
use Nette\Utils\Html;
/**
* Runtime helpers for Latte.
* @internal
*/
class Runtime
{
use Nette\StaticClass;
/**
* Renders form begin.
* @return string
*/
public static function renderFormBegin(Form $form, array $attrs, $withTags = true)
{
$form->fireRenderEvents();
foreach ($form->getControls() as $control) {
$control->setOption('rendered', false);
}
$el = $form->getElementPrototype();
$el->action = (string) $el->action;
$el = clone $el;
if ($form->isMethod('get')) {
$el->action = preg_replace('~\?[^#]*~', '', $el->action, 1);
}
$el->addAttributes($attrs);
return $withTags ? $el->startTag() : $el->attributes();
}
/**
* Renders form end.
* @return string
*/
public static function renderFormEnd(Form $form, $withTags = true)
{
$s = '';
if ($form->isMethod('get')) {
foreach (preg_split('#[;&]#', (string) parse_url($form->getElementPrototype()->action, PHP_URL_QUERY), -1, PREG_SPLIT_NO_EMPTY) as $param) {
$parts = explode('=', $param, 2);
$name = urldecode($parts[0]);
$prefix = explode('[', $name, 2)[0];
if (!isset($form[$prefix])) {
$s .= Html::el('input', ['type' => 'hidden', 'name' => $name, 'value' => urldecode($parts[1])]);
}
}
}
foreach ($form->getControls() as $control) {
if ($control->getOption('type') === 'hidden' && !$control->getOption('rendered')) {
$s .= $control->getControl();
}
}
if (iterator_count($form->getComponents(true, Nette\Forms\Controls\TextInput::class)) < 2) {
$s .= "<!--[if IE]><input type=IEbug disabled style=\"display:none\"><![endif]-->\n";
}
return $s . ($withTags ? $form->getElementPrototype()->endTag() . "\n" : '');
}
}
+545
View File
@@ -0,0 +1,545 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms;
use Nette;
/**
* Container for form controls.
*
* @property Nette\Utils\ArrayHash $values
* @property-read \Iterator $controls
* @property-read Form|null $form
*/
class Container extends Nette\ComponentModel\Container implements \ArrayAccess
{
/** @var callable[] function (Container $sender); Occurs when the form is validated */
public $onValidate;
/** @var ControlGroup|null */
protected $currentGroup;
/** @var bool */
private $validated;
/********************* data exchange ****************d*g**/
/**
* Fill-in with default values.
* @param iterable
* @param bool
* @return static
*/
public function setDefaults($values, $erase = false)
{
$form = $this->getForm(false);
if (!$form || !$form->isAnchored() || !$form->isSubmitted()) {
$this->setValues($values, $erase);
}
return $this;
}
/**
* Fill-in with values.
* @param iterable
* @param bool
* @return static
* @internal
*/
public function setValues($values, $erase = false)
{
if ($values instanceof \Traversable) {
$values = iterator_to_array($values);
} elseif (!is_array($values)) {
throw new Nette\InvalidArgumentException(sprintf('First parameter must be an array, %s given.', gettype($values)));
}
foreach ($this->getComponents() as $name => $control) {
if ($control instanceof IControl) {
if (array_key_exists($name, $values)) {
$control->setValue($values[$name]);
} elseif ($erase) {
$control->setValue(null);
}
} elseif ($control instanceof self) {
if (array_key_exists($name, $values)) {
$control->setValues($values[$name], $erase);
} elseif ($erase) {
$control->setValues([], $erase);
}
}
}
return $this;
}
/**
* Returns the values submitted by the form.
* @param bool
* @return Nette\Utils\ArrayHash|array
*/
public function getValues($asArray = false)
{
$values = $asArray ? [] : new Nette\Utils\ArrayHash;
foreach ($this->getComponents() as $name => $control) {
if ($control instanceof IControl && !$control->isOmitted()) {
$values[$name] = $control->getValue();
} elseif ($control instanceof self) {
$values[$name] = $control->getValues($asArray);
}
}
return $values;
}
/********************* validation ****************d*g**/
/**
* Is form valid?
* @return bool
*/
public function isValid()
{
if (!$this->validated) {
if ($this->getErrors()) {
return false;
}
$this->validate();
}
return !$this->getErrors();
}
/**
* Performs the server side validation.
* @param IControl[]
* @return void
*/
public function validate(array $controls = null)
{
foreach ($controls === null ? $this->getComponents() : $controls as $control) {
if ($control instanceof IControl || $control instanceof self) {
$control->validate();
}
}
if ($this->onValidate !== null) {
if (!is_array($this->onValidate) && !$this->onValidate instanceof \Traversable) {
throw new Nette\UnexpectedValueException('Property Form::$onValidate must be array or Traversable, ' . gettype($this->onValidate) . ' given.');
}
foreach ($this->onValidate as $handler) {
$params = Nette\Utils\Callback::toReflection($handler)->getParameters();
$values = isset($params[1]) ? $this->getValues($params[1]->isArray()) : null;
Nette\Utils\Callback::invoke($handler, $this, $values);
}
}
$this->validated = true;
}
/**
* Returns all validation errors.
* @return array
*/
public function getErrors()
{
$errors = [];
foreach ($this->getControls() as $control) {
$errors = array_merge($errors, $control->getErrors());
}
return array_unique($errors);
}
/********************* form building ****************d*g**/
/**
* @return static
*/
public function setCurrentGroup(ControlGroup $group = null)
{
$this->currentGroup = $group;
return $this;
}
/**
* Returns current group.
* @return ControlGroup|null
*/
public function getCurrentGroup()
{
return $this->currentGroup;
}
/**
* Adds the specified component to the IContainer.
* @param Nette\ComponentModel\IComponent
* @param string|int
* @param string
* @return static
* @throws Nette\InvalidStateException
*/
public function addComponent(Nette\ComponentModel\IComponent $component, $name, $insertBefore = null)
{
parent::addComponent($component, $name, $insertBefore);
if ($this->currentGroup !== null) {
$this->currentGroup->add($component);
}
return $this;
}
/**
* Iterates over all form controls.
* @return \Iterator
*/
public function getControls()
{
return $this->getComponents(true, IControl::class);
}
/**
* Returns form.
* @param bool
* @return Form|null
*/
public function getForm($throw = true)
{
return $this->lookup(Form::class, $throw);
}
/********************* control factories ****************d*g**/
/**
* Adds single-line text input control to the form.
* @param string
* @param string|object
* @param int
* @param int
* @return Controls\TextInput
*/
public function addText($name, $label = null, $cols = null, $maxLength = null)
{
return $this[$name] = (new Controls\TextInput($label, $maxLength))
->setHtmlAttribute('size', $cols);
}
/**
* Adds single-line text input control used for sensitive input such as passwords.
* @param string
* @param string|object
* @param int
* @param int
* @return Controls\TextInput
*/
public function addPassword($name, $label = null, $cols = null, $maxLength = null)
{
return $this[$name] = (new Controls\TextInput($label, $maxLength))
->setHtmlAttribute('size', $cols)
->setHtmlType('password');
}
/**
* Adds multi-line text input control to the form.
* @param string
* @param string|object
* @param int
* @param int
* @return Controls\TextArea
*/
public function addTextArea($name, $label = null, $cols = null, $rows = null)
{
return $this[$name] = (new Controls\TextArea($label))
->setHtmlAttribute('cols', $cols)->setHtmlAttribute('rows', $rows);
}
/**
* Adds input for email.
* @param string
* @param string|object
* @return Controls\TextInput
*/
public function addEmail($name, $label = null)
{
return $this[$name] = (new Controls\TextInput($label))
->setRequired(false)
->addRule(Form::EMAIL);
}
/**
* Adds input for integer.
* @param string
* @param string|object
* @return Controls\TextInput
*/
public function addInteger($name, $label = null)
{
return $this[$name] = (new Controls\TextInput($label))
->setNullable()
->setRequired(false)
->addRule(Form::INTEGER);
}
/**
* Adds control that allows the user to upload files.
* @param string
* @param string|object
* @param bool
* @return Controls\UploadControl
*/
public function addUpload($name, $label = null, $multiple = false)
{
return $this[$name] = new Controls\UploadControl($label, $multiple);
}
/**
* Adds control that allows the user to upload multiple files.
* @param string
* @param string|object
* @return Controls\UploadControl
*/
public function addMultiUpload($name, $label = null)
{
return $this[$name] = new Controls\UploadControl($label, true);
}
/**
* Adds hidden form control used to store a non-displayed value.
* @param string
* @param string
* @return Controls\HiddenField
*/
public function addHidden($name, $default = null)
{
return $this[$name] = (new Controls\HiddenField)
->setDefaultValue($default);
}
/**
* Adds check box control to the form.
* @param string
* @param string|object
* @return Controls\Checkbox
*/
public function addCheckbox($name, $caption = null)
{
return $this[$name] = new Controls\Checkbox($caption);
}
/**
* Adds set of radio button controls to the form.
* @param string
* @param string|object
* @return Controls\RadioList
*/
public function addRadioList($name, $label = null, array $items = null)
{
return $this[$name] = new Controls\RadioList($label, $items);
}
/**
* Adds set of checkbox controls to the form.
* @param string
* @param string|object
* @return Controls\CheckboxList
*/
public function addCheckboxList($name, $label = null, array $items = null)
{
return $this[$name] = new Controls\CheckboxList($label, $items);
}
/**
* Adds select box control that allows single item selection.
* @param string
* @param string|object
* @param array
* @param int
* @return Controls\SelectBox
*/
public function addSelect($name, $label = null, array $items = null, $size = null)
{
return $this[$name] = (new Controls\SelectBox($label, $items))
->setHtmlAttribute('size', $size > 1 ? (int) $size : null);
}
/**
* Adds select box control that allows multiple item selection.
* @param string
* @param string|object
* @param array
* @param int
* @return Controls\MultiSelectBox
*/
public function addMultiSelect($name, $label = null, array $items = null, $size = null)
{
return $this[$name] = (new Controls\MultiSelectBox($label, $items))
->setHtmlAttribute('size', $size > 1 ? (int) $size : null);
}
/**
* Adds button used to submit form.
* @param string
* @param string|object
* @return Controls\SubmitButton
*/
public function addSubmit($name, $caption = null)
{
return $this[$name] = new Controls\SubmitButton($caption);
}
/**
* Adds push buttons with no default behavior.
* @param string
* @param string|object
* @return Controls\Button
*/
public function addButton($name, $caption = null)
{
return $this[$name] = new Controls\Button($caption);
}
/**
* Adds graphical button used to submit form.
* @param string
* @param string URI of the image
* @param string alternate text for the image
* @return Controls\ImageButton
*/
public function addImage($name, $src = null, $alt = null)
{
return $this[$name] = new Controls\ImageButton($src, $alt);
}
/**
* Adds naming container to the form.
* @param string|int
* @return self
*/
public function addContainer($name)
{
$control = new self;
$control->currentGroup = $this->currentGroup;
if ($this->currentGroup !== null) {
$this->currentGroup->add($control);
}
return $this[$name] = $control;
}
/********************* extension methods ****************d*g**/
public function __call($name, $args)
{
if ($callback = Nette\Utils\ObjectMixin::getExtensionMethod(__CLASS__, $name)) {
return Nette\Utils\Callback::invoke($callback, $this, ...$args);
}
return parent::__call($name, $args);
}
public static function extensionMethod($name, $callback = null)
{
if (strpos($name, '::') !== false) { // back compatibility
list(, $name) = explode('::', $name);
}
Nette\Utils\ObjectMixin::setExtensionMethod(__CLASS__, $name, $callback);
}
/********************* interface \ArrayAccess ****************d*g**/
/**
* Adds the component to the container.
* @param string|int
* @param Nette\ComponentModel\IComponent
* @return void
*/
public function offsetSet($name, $component)
{
$this->addComponent($component, $name);
}
/**
* Returns component specified by name. Throws exception if component doesn't exist.
* @param string|int
* @return Nette\ComponentModel\IComponent
* @throws Nette\InvalidArgumentException
*/
public function offsetGet($name)
{
return $this->getComponent($name, true);
}
/**
* Does component specified by name exists?
* @param string|int
* @return bool
*/
public function offsetExists($name)
{
return $this->getComponent($name, false) !== null;
}
/**
* Removes component from the container.
* @param string|int
* @return void
*/
public function offsetUnset($name)
{
$component = $this->getComponent($name, false);
if ($component !== null) {
$this->removeComponent($component);
}
}
/**
* Prevents cloning.
*/
public function __clone()
{
throw new Nette\NotImplementedException('Form cloning is not supported yet.');
}
}
+134
View File
@@ -0,0 +1,134 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms;
use Nette;
/**
* A user group of form controls.
*/
class ControlGroup
{
use Nette\SmartObject;
/** @var \SplObjectStorage */
protected $controls;
/** @var array user options */
private $options = [];
public function __construct()
{
$this->controls = new \SplObjectStorage;
}
/**
* @return static
*/
public function add(...$items)
{
foreach ($items as $item) {
if ($item instanceof IControl) {
$this->controls->attach($item);
} elseif ($item instanceof Container) {
foreach ($item->getComponents() as $component) {
$this->add($component);
}
} elseif ($item instanceof \Traversable || is_array($item)) {
$this->add(...$item);
} else {
$type = is_object($item) ? get_class($item) : gettype($item);
throw new Nette\InvalidArgumentException("IControl or Container items expected, $type given.");
}
}
return $this;
}
/**
* @return void
*/
public function remove(IControl $control)
{
$this->controls->detach($control);
}
/**
* @return void
*/
public function removeOrphans()
{
foreach ($this->controls as $control) {
if (!$control->getForm(false)) {
$this->controls->detach($control);
}
}
}
/**
* @return IControl[]
*/
public function getControls()
{
return iterator_to_array($this->controls);
}
/**
* Sets user-specific option.
* Options recognized by DefaultFormRenderer
* - 'label' - textual or IHtmlString object label
* - 'visual' - indicates visual group
* - 'container' - container as Html object
* - 'description' - textual or IHtmlString object description
* - 'embedNext' - describes how render next group
*
* @param string
* @param mixed
* @return static
*/
public function setOption($key, $value)
{
if ($value === null) {
unset($this->options[$key]);
} else {
$this->options[$key] = $value;
}
return $this;
}
/**
* Returns user-specific option.
* @param string
* @param mixed
* @return mixed
*/
public function getOption($key, $default = null)
{
return isset($this->options[$key]) ? $this->options[$key] : $default;
}
/**
* Returns user-specific options.
* @return array
*/
public function getOptions()
{
return $this->options;
}
}
+619
View File
@@ -0,0 +1,619 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms\Controls;
use Nette;
use Nette\Forms\Form;
use Nette\Forms\IControl;
use Nette\Forms\Rules;
use Nette\Utils\Html;
/**
* Base class that implements the basic functionality common to form controls.
*
* @property-read Form $form
* @property-read string $htmlName
* @property mixed $htmlId
* @property mixed $value
* @property bool $disabled
* @property bool $omitted
* @property-read Html $control
* @property-read Html $label
* @property-read Html $controlPrototype
* @property-read Html $labelPrototype
* @property bool $required
* @property-read bool $filled
* @property-read array $errors
* @property-read array $options
* @property-read string $error
*/
abstract class BaseControl extends Nette\ComponentModel\Component implements IControl
{
/** @var string */
public static $idMask = 'frm-%s';
/** @var string|object textual caption or label */
public $caption;
/** @var mixed current control value */
protected $value;
/** @var Html control element template */
protected $control;
/** @var Html label element template */
protected $label;
/** @var bool */
protected $disabled = false;
/** @var array */
private $errors = [];
/** @var bool|null */
private $omitted;
/** @var Rules */
private $rules;
/** @var Nette\Localization\ITranslator */
private $translator = true; // means autodetect
/** @var array user options */
private $options = [];
/** @var bool */
private static $autoOptional = false;
/**
* @param string|object
*/
public function __construct($caption = null)
{
$this->monitor(Form::class);
parent::__construct();
$this->control = Html::el('input', ['type' => null, 'name' => null]);
$this->label = Html::el('label');
$this->caption = $caption;
$this->rules = new Rules($this);
if (self::$autoOptional) {
$this->setRequired(false);
}
$this->setValue(null);
}
/**
* This method will be called when the component becomes attached to Form.
* @param Nette\ComponentModel\IComponent
* @return void
*/
protected function attached($form)
{
if (!$this->isDisabled() && $form instanceof Form && $form->isAnchored() && $form->isSubmitted()) {
$this->loadHttpData();
}
}
/**
* Returns form.
* @param bool
* @return Form|null
*/
public function getForm($throw = true)
{
return $this->lookup(Form::class, $throw);
}
/**
* Loads HTTP data.
* @return void
*/
public function loadHttpData()
{
$this->setValue($this->getHttpData(Form::DATA_TEXT));
}
/**
* Loads HTTP data.
* @return mixed
*/
protected function getHttpData($type, $htmlTail = null)
{
return $this->getForm()->getHttpData($type, $this->getHtmlName() . $htmlTail);
}
/**
* Returns HTML name of control.
* @return string
*/
public function getHtmlName()
{
return Nette\Forms\Helpers::generateHtmlName($this->lookupPath(Form::class));
}
/********************* interface IControl ****************d*g**/
/**
* Sets control's value.
* @return static
* @internal
*/
public function setValue($value)
{
$this->value = $value;
return $this;
}
/**
* Returns control's value.
* @return mixed
*/
public function getValue()
{
return $this->value;
}
/**
* Is control filled?
* @return bool
*/
public function isFilled()
{
$value = $this->getValue();
return $value !== null && $value !== [] && $value !== '';
}
/**
* Sets control's default value.
* @return static
*/
public function setDefaultValue($value)
{
$form = $this->getForm(false);
if ($this->isDisabled() || !$form || !$form->isAnchored() || !$form->isSubmitted()) {
$this->setValue($value);
}
return $this;
}
/**
* Disables or enables control.
* @param bool
* @return static
*/
public function setDisabled($value = true)
{
if ($this->disabled = (bool) $value) {
$this->setValue(null);
} elseif (($form = $this->getForm(false)) && $form->isAnchored() && $form->isSubmitted()) {
$this->loadHttpData();
}
return $this;
}
/**
* Is control disabled?
* @return bool
*/
public function isDisabled()
{
return $this->disabled === true;
}
/**
* Sets whether control value is excluded from $form->getValues() result.
* @param bool
* @return static
*/
public function setOmitted($value = true)
{
$this->omitted = (bool) $value;
return $this;
}
/**
* Is control value excluded from $form->getValues() result?
* @return bool
*/
public function isOmitted()
{
return $this->omitted || ($this->isDisabled() && $this->omitted === null);
}
/********************* rendering ****************d*g**/
/**
* Generates control's HTML element.
* @return Html|string
*/
public function getControl()
{
$this->setOption('rendered', true);
$el = clone $this->control;
return $el->addAttributes([
'name' => $this->getHtmlName(),
'id' => $this->getHtmlId(),
'required' => $this->isRequired(),
'disabled' => $this->isDisabled(),
'data-nette-rules' => Nette\Forms\Helpers::exportRules($this->rules) ?: null,
]);
}
/**
* Generates label's HTML element.
* @param string|object
* @return Html|string
*/
public function getLabel($caption = null)
{
$label = clone $this->label;
$label->for = $this->getHtmlId();
$label->setText($this->translate($caption === null ? $this->caption : $caption));
return $label;
}
/**
* @return Nette\Utils\Html|null
*/
public function getControlPart()
{
return $this->getControl();
}
/**
* @return Nette\Utils\Html|null
*/
public function getLabelPart()
{
return $this->getLabel();
}
/**
* Returns control's HTML element template.
* @return Html
*/
public function getControlPrototype()
{
return $this->control;
}
/**
* Returns label's HTML element template.
* @return Html
*/
public function getLabelPrototype()
{
return $this->label;
}
/**
* Changes control's HTML id.
* @param mixed new ID, or false or null
* @return static
*/
public function setHtmlId($id)
{
$this->control->id = $id;
return $this;
}
/**
* Returns control's HTML id.
* @return mixed
*/
public function getHtmlId()
{
if (!isset($this->control->id)) {
$form = $this->getForm(false);
$this->control->id = sprintf(self::$idMask, $this->lookupPath(), $form ? $form->getName() : '');
}
return $this->control->id;
}
/**
* Changes control's HTML attribute.
* @param string
* @param mixed
* @return static
*/
public function setHtmlAttribute($name, $value = true)
{
return $this->setAttribute($name, $value);
}
/**
* Alias for setHtmlAttribute()
* @param string
* @param mixed
* @return static
*/
public function setAttribute($name, $value = true)
{
$this->control->$name = $value;
return $this;
}
/********************* translator ****************d*g**/
/**
* Sets translate adapter.
* @return static
*/
public function setTranslator(Nette\Localization\ITranslator $translator = null)
{
$this->translator = $translator;
return $this;
}
/**
* Returns translate adapter.
* @return Nette\Localization\ITranslator|null
*/
public function getTranslator()
{
if ($this->translator === true) {
return $this->getForm(false) ? $this->getForm()->getTranslator() : null;
}
return $this->translator;
}
/**
* Returns translated string.
* @param mixed
* @param int plural count
* @return mixed
*/
public function translate($value, $count = null)
{
if ($translator = $this->getTranslator()) {
$tmp = is_array($value) ? [&$value] : [[&$value]];
foreach ($tmp[0] as &$v) {
if ($v != null && !$v instanceof Html) { // intentionally ==
$v = $translator->translate($v, $count);
}
}
}
return $value;
}
/********************* rules ****************d*g**/
/**
* Adds a validation rule.
* @param mixed
* @param string|object
* @param mixed
* @return static
*/
public function addRule($validator, $errorMessage = null, $arg = null)
{
$this->rules->addRule($validator, $errorMessage, $arg);
return $this;
}
/**
* Adds a validation condition a returns new branch.
* @param mixed
* @param mixed
* @return Rules new branch
*/
public function addCondition($validator, $value = null)
{
return $this->rules->addCondition($validator, $value);
}
/**
* Adds a validation condition based on another control a returns new branch.
* @param IControl
* @param mixed
* @param mixed
* @return Rules new branch
*/
public function addConditionOn(IControl $control, $validator, $value = null)
{
return $this->rules->addConditionOn($control, $validator, $value);
}
/**
* @return Rules
*/
public function getRules()
{
return $this->rules;
}
/**
* Makes control mandatory.
* @param mixed state or error message
* @return static
*/
public function setRequired($value = true)
{
$this->rules->setRequired($value);
return $this;
}
/**
* Is control mandatory?
* @return bool
*/
public function isRequired()
{
return $this->rules->isRequired();
}
/**
* Performs the server side validation.
* @return void
*/
public function validate()
{
if ($this->isDisabled()) {
return;
}
$this->cleanErrors();
$this->rules->validate();
}
/**
* Adds error message to the list.
* @param string|object
* @return void
*/
public function addError($message, $translate = true)
{
$this->errors[] = $translate ? $this->translate($message) : $message;
}
/**
* Returns errors corresponding to control.
* @return string|null
*/
public function getError()
{
return $this->errors ? implode(' ', array_unique($this->errors)) : null;
}
/**
* Returns errors corresponding to control.
* @return array
*/
public function getErrors()
{
return array_unique($this->errors);
}
/**
* @return bool
*/
public function hasErrors()
{
return (bool) $this->errors;
}
/**
* @return void
*/
public function cleanErrors()
{
$this->errors = [];
}
/**
* Globally enables new required/optional behavior.
* This method will be deprecated in next version.
*/
public static function enableAutoOptionalMode()
{
self::$autoOptional = true;
}
/********************* user data ****************d*g**/
/**
* Sets user-specific option.
* @return static
*/
public function setOption($key, $value)
{
if ($value === null) {
unset($this->options[$key]);
} else {
$this->options[$key] = $value;
}
return $this;
}
/**
* Returns user-specific option.
* @return mixed
*/
public function getOption($key, $default = null)
{
return isset($this->options[$key]) ? $this->options[$key] : $default;
}
/**
* Returns user-specific options.
* @return array
*/
public function getOptions()
{
return $this->options;
}
/********************* extension methods ****************d*g**/
public function __call($name, $args)
{
if ($callback = Nette\Utils\ObjectMixin::getExtensionMethod(get_class($this), $name)) {
return Nette\Utils\Callback::invoke($callback, $this, ...$args);
}
return parent::__call($name, $args);
}
public static function extensionMethod($name, $callback = null)
{
if (strpos($name, '::') !== false) { // back compatibility
list(, $name) = explode('::', $name);
}
Nette\Utils\ObjectMixin::setExtensionMethod(get_called_class(), $name, $callback);
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms\Controls;
use Nette;
/**
* Push button control with no default behavior.
*/
class Button extends BaseControl
{
/**
* @param string|object
*/
public function __construct($caption = null)
{
parent::__construct($caption);
$this->control->type = 'button';
$this->setOption('type', 'button');
}
/**
* Is button pressed?
* @return bool
*/
public function isFilled()
{
$value = $this->getValue();
return $value !== null && $value !== [];
}
/**
* Bypasses label generation.
* @return void
*/
public function getLabel($caption = null)
{
}
/**
* Generates control's HTML element.
* @param string|object
* @return Nette\Utils\Html
*/
public function getControl($caption = null)
{
$this->setOption('rendered', true);
$el = clone $this->control;
return $el->addAttributes([
'name' => $this->getHtmlName(),
'disabled' => $this->isDisabled(),
'value' => $this->translate($caption === null ? $this->caption : $caption),
]);
}
}
+106
View File
@@ -0,0 +1,106 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms\Controls;
use Nette;
use Nette\Utils\Html;
/**
* Check box control. Allows the user to select a true or false condition.
*/
class Checkbox extends BaseControl
{
/** @var Html wrapper element template */
private $wrapper;
/**
* @param string|object
*/
public function __construct($label = null)
{
parent::__construct($label);
$this->control->type = 'checkbox';
$this->wrapper = Html::el();
$this->setOption('type', 'checkbox');
}
/**
* Sets control's value.
* @param bool
* @return static
* @internal
*/
public function setValue($value)
{
if (!is_scalar($value) && $value !== null) {
throw new Nette\InvalidArgumentException(sprintf("Value must be scalar or null, %s given in field '%s'.", gettype($value), $this->name));
}
$this->value = (bool) $value;
return $this;
}
/**
* Is control filled?
* @return bool
*/
public function isFilled()
{
return $this->getValue() !== false; // back compatibility
}
/**
* Generates control's HTML element.
* @return Html
*/
public function getControl()
{
return $this->wrapper->setHtml($this->getLabelPart()->insert(0, $this->getControlPart()));
}
/**
* Bypasses label generation.
* @return void
*/
public function getLabel($caption = null)
{
}
/**
* @return Html
*/
public function getControlPart()
{
return parent::getControl()->checked($this->value);
}
/**
* @return Html
*/
public function getLabelPart()
{
return parent::getLabel();
}
/**
* Returns wrapper HTML element template.
* @return Html
*/
public function getSeparatorPrototype()
{
return $this->wrapper;
}
}
+141
View File
@@ -0,0 +1,141 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms\Controls;
use Nette;
use Nette\Utils\Html;
/**
* Set of checkboxes.
*
* @property-read Html $separatorPrototype
* @property-read Html $containerPrototype
* @property-read Html $itemLabelPrototype
*/
class CheckboxList extends MultiChoiceControl
{
/** @var Html separator element template */
protected $separator;
/** @var Html container element template */
protected $container;
/** @var Html item label template */
protected $itemLabel;
/**
* @param string|object
*/
public function __construct($label = null, array $items = null)
{
parent::__construct($label, $items);
$this->control->type = 'checkbox';
$this->container = Html::el();
$this->separator = Html::el('br');
// $this->itemLabel = Html::el('label'); back compatiblity
$this->setOption('type', 'checkbox');
}
/**
* Generates control's HTML element.
* @return Html
*/
public function getControl()
{
$input = parent::getControl();
$items = $this->getItems();
reset($items);
return $this->container->setHtml(
Nette\Forms\Helpers::createInputList(
$this->translate($items),
array_merge($input->attrs, [
'id' => null,
'checked?' => $this->value,
'disabled:' => $this->disabled,
'required' => null,
'data-nette-rules:' => [key($items) => $input->attrs['data-nette-rules']],
]),
$this->itemLabel ? $this->itemLabel->attrs : $this->label->attrs,
$this->separator
)
);
}
/**
* Generates label's HTML element.
* @param string|object
* @return Html
*/
public function getLabel($caption = null)
{
return parent::getLabel($caption)->for(null);
}
/**
* @return Html
*/
public function getControlPart($key = null)
{
$key = key([(string) $key => null]);
return parent::getControl()->addAttributes([
'id' => $this->getHtmlId() . '-' . $key,
'checked' => in_array($key, (array) $this->value, true),
'disabled' => is_array($this->disabled) ? isset($this->disabled[$key]) : $this->disabled,
'required' => null,
'value' => $key,
]);
}
/**
* @return Html
*/
public function getLabelPart($key = null)
{
$itemLabel = $this->itemLabel ? clone $this->itemLabel : clone $this->label;
return func_num_args()
? $itemLabel->setText($this->translate($this->items[$key]))->for($this->getHtmlId() . '-' . $key)
: $this->getLabel();
}
/**
* Returns separator HTML element template.
* @return Html
*/
public function getSeparatorPrototype()
{
return $this->separator;
}
/**
* Returns container HTML element template.
* @return Html
*/
public function getContainerPrototype()
{
return $this->container;
}
/**
* Returns item label HTML element template.
* @return Html
*/
public function getItemLabelPrototype()
{
return $this->itemLabel ?: $this->itemLabel = Html::el('label');
}
}
+166
View File
@@ -0,0 +1,166 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms\Controls;
use Nette;
/**
* Choice control that allows single item selection.
*
* @property array $items
* @property-read mixed $selectedItem
*/
abstract class ChoiceControl extends BaseControl
{
/**
* @var bool
* @deprecated use checkDefaultValue()
*/
public $checkAllowedValues = true;
/** @var array */
private $items = [];
public function __construct($label = null, array $items = null)
{
parent::__construct($label);
if ($items !== null) {
$this->setItems($items);
}
}
/**
* Loads HTTP data.
* @return void
*/
public function loadHttpData()
{
$this->value = $this->getHttpData(Nette\Forms\Form::DATA_TEXT);
if ($this->value !== null) {
if (is_array($this->disabled) && isset($this->disabled[$this->value])) {
$this->value = null;
} else {
$this->value = key([$this->value => null]);
}
}
}
/**
* Sets selected item (by key).
* @param string|int
* @return static
* @internal
*/
public function setValue($value)
{
if ($this->checkAllowedValues && $value !== null && !array_key_exists((string) $value, $this->items)) {
$set = Nette\Utils\Strings::truncate(implode(', ', array_map(function ($s) { return var_export($s, true); }, array_keys($this->items))), 70, '...');
throw new Nette\InvalidArgumentException("Value '$value' is out of allowed set [$set] in field '{$this->name}'.");
}
$this->value = $value === null ? null : key([(string) $value => null]);
return $this;
}
/**
* Returns selected key.
* @return string|int
*/
public function getValue()
{
return array_key_exists($this->value, $this->items) ? $this->value : null;
}
/**
* Returns selected key (not checked).
* @return string|int
*/
public function getRawValue()
{
return $this->value;
}
/**
* Is any item selected?
* @return bool
*/
public function isFilled()
{
return $this->getValue() !== null;
}
/**
* Sets items from which to choose.
* @param array
* @param bool
* @return static
*/
public function setItems(array $items, $useKeys = true)
{
$this->items = $useKeys ? $items : array_combine($items, $items);
return $this;
}
/**
* Returns items from which to choose.
* @return array
*/
public function getItems()
{
return $this->items;
}
/**
* Returns selected value.
* @return mixed
*/
public function getSelectedItem()
{
$value = $this->getValue();
return $value === null ? null : $this->items[$value];
}
/**
* Disables or enables control or items.
* @param bool|array
* @return static
*/
public function setDisabled($value = true)
{
if (!is_array($value)) {
return parent::setDisabled($value);
}
parent::setDisabled(false);
$this->disabled = array_fill_keys($value, true);
if (isset($this->disabled[$this->value])) {
$this->value = null;
}
return $this;
}
/**
* @return static
*/
public function checkDefaultValue($value = true)
{
$this->checkAllowedValues = $value;
return $this;
}
}
+125
View File
@@ -0,0 +1,125 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms\Controls;
use Nette;
/**
* CSRF protection field.
*/
class CsrfProtection extends HiddenField
{
const PROTECTION = 'Nette\Forms\Controls\CsrfProtection::validateCsrf';
/** @var Nette\Http\Session */
public $session;
/**
* @param string|object
*/
public function __construct($errorMessage)
{
parent::__construct();
$this->setOmitted()
->setRequired()
->addRule(self::PROTECTION, $errorMessage);
$this->monitor(Nette\Application\UI\Presenter::class);
}
protected function attached($parent)
{
parent::attached($parent);
if (!$this->session && $parent instanceof Nette\Application\UI\Presenter) {
$this->session = $parent->getSession();
}
}
/**
* @return static
* @internal
*/
public function setValue($value)
{
return $this;
}
/**
* Loads HTTP data.
* @return void
*/
public function loadHttpData()
{
$this->value = $this->getHttpData(Nette\Forms\Form::DATA_TEXT);
}
/**
* @return string
*/
public function getToken()
{
$session = $this->getSession()->getSection(__CLASS__);
if (!isset($session->token)) {
$session->token = Nette\Utils\Random::generate();
}
return $session->token ^ $this->getSession()->getId();
}
/**
* @return string
*/
private function generateToken($random = null)
{
if ($random === null) {
$random = Nette\Utils\Random::generate(10);
}
return $random . base64_encode(sha1($this->getToken() . $random, true));
}
/**
* Generates control's HTML element.
* @return Nette\Utils\Html
*/
public function getControl()
{
return parent::getControl()->value($this->generateToken());
}
/**
* @return bool
* @internal
*/
public static function validateCsrf(self $control)
{
$value = (string) $control->getValue();
return $control->generateToken(substr($value, 0, 10)) === $value;
}
/********************* backend ****************d*g**/
/**
* @return Nette\Http\Session
*/
private function getSession()
{
if (!$this->session) {
$this->session = new Nette\Http\Session($this->getForm()->httpRequest, new Nette\Http\Response);
}
return $this->session;
}
}
+88
View File
@@ -0,0 +1,88 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms\Controls;
use Nette;
/**
* Hidden form control used to store a non-displayed value.
*/
class HiddenField extends BaseControl
{
/** @var bool */
private $persistValue;
public function __construct($persistentValue = null)
{
parent::__construct();
$this->control->type = 'hidden';
$this->setOption('type', 'hidden');
if ($persistentValue !== null) {
$this->unmonitor(Nette\Forms\Form::class);
$this->persistValue = true;
$this->value = (string) $persistentValue;
}
}
/**
* Sets control's value.
* @param string
* @return static
* @internal
*/
public function setValue($value)
{
if (!is_scalar($value) && $value !== null && !method_exists($value, '__toString')) {
throw new Nette\InvalidArgumentException(sprintf("Value must be scalar or null, %s given in field '%s'.", gettype($value), $this->name));
}
if (!$this->persistValue) {
$this->value = (string) $value;
}
return $this;
}
/**
* Generates control's HTML element.
* @return Nette\Utils\Html
*/
public function getControl()
{
$this->setOption('rendered', true);
$el = clone $this->control;
return $el->addAttributes([
'name' => $this->getHtmlName(),
'disabled' => $this->isDisabled(),
'value' => $this->value,
]);
}
/**
* Bypasses label generation.
* @param string|object
* @return void
*/
public function getLabel($caption = null)
{
}
/**
* Adds error message to the list.
* @param string|object
* @return void
*/
public function addError($message, $translate = true)
{
$this->getForm()->addError($message, $translate);
}
}
+51
View File
@@ -0,0 +1,51 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms\Controls;
/**
* Submittable image button form control.
*/
class ImageButton extends SubmitButton
{
/**
* @param string URI of the image
* @param string alternate text for the image
*/
public function __construct($src = null, $alt = null)
{
parent::__construct();
$this->control->type = 'image';
$this->control->src = $src;
$this->control->alt = $alt;
}
/**
* Loads HTTP data.
* @return void
*/
public function loadHttpData()
{
parent::loadHttpData();
$this->value = $this->value
? [(int) array_shift($this->value), (int) array_shift($this->value)]
: null;
}
/**
* Returns HTML name of control.
* @return string
*/
public function getHtmlName()
{
return parent::getHtmlName() . '[]';
}
}
@@ -0,0 +1,183 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms\Controls;
use Nette;
/**
* Choice control that allows multiple items selection.
*
* @property array $items
* @property-read array $selectedItems
*/
abstract class MultiChoiceControl extends BaseControl
{
/**
* @var bool
* @deprecated use checkDefaultValue()
*/
public $checkAllowedValues = true;
/** @var array */
private $items = [];
public function __construct($label = null, array $items = null)
{
parent::__construct($label);
if ($items !== null) {
$this->setItems($items);
}
}
/**
* Loads HTTP data.
* @return void
*/
public function loadHttpData()
{
$this->value = array_keys(array_flip($this->getHttpData(Nette\Forms\Form::DATA_TEXT)));
if (is_array($this->disabled)) {
$this->value = array_diff($this->value, array_keys($this->disabled));
}
}
/**
* Sets selected items (by keys).
* @param array
* @return static
* @internal
*/
public function setValue($values)
{
if (is_scalar($values) || $values === null) {
$values = (array) $values;
} elseif (!is_array($values)) {
throw new Nette\InvalidArgumentException(sprintf("Value must be array or null, %s given in field '%s'.", gettype($values), $this->name));
}
$flip = [];
foreach ($values as $value) {
if (!is_scalar($value) && !method_exists($value, '__toString')) {
throw new Nette\InvalidArgumentException(sprintf("Values must be scalar, %s given in field '%s'.", gettype($value), $this->name));
}
$flip[(string) $value] = true;
}
$values = array_keys($flip);
if ($this->checkAllowedValues && ($diff = array_diff($values, array_keys($this->items)))) {
$set = Nette\Utils\Strings::truncate(implode(', ', array_map(function ($s) { return var_export($s, true); }, array_keys($this->items))), 70, '...');
$vals = (count($diff) > 1 ? 's' : '') . " '" . implode("', '", $diff) . "'";
throw new Nette\InvalidArgumentException("Value$vals are out of allowed set [$set] in field '{$this->name}'.");
}
$this->value = $values;
return $this;
}
/**
* Returns selected keys.
* @return array
*/
public function getValue()
{
return array_values(array_intersect($this->value, array_keys($this->items)));
}
/**
* Returns selected keys (not checked).
* @return array
*/
public function getRawValue()
{
return $this->value;
}
/**
* Is any item selected?
* @return bool
*/
public function isFilled()
{
return $this->getValue() !== [];
}
/**
* Sets items from which to choose.
* @param array
* @param bool
* @return static
*/
public function setItems(array $items, $useKeys = true)
{
$this->items = $useKeys ? $items : array_combine($items, $items);
return $this;
}
/**
* Returns items from which to choose.
* @return array
*/
public function getItems()
{
return $this->items;
}
/**
* Returns selected values.
* @return array
*/
public function getSelectedItems()
{
return array_intersect_key($this->items, array_flip($this->value));
}
/**
* Disables or enables control or items.
* @param bool|array
* @return static
*/
public function setDisabled($value = true)
{
if (!is_array($value)) {
return parent::setDisabled($value);
}
parent::setDisabled(false);
$this->disabled = array_fill_keys($value, true);
$this->value = array_diff($this->value, $value);
return $this;
}
/**
* Returns HTML name of control.
* @return string
*/
public function getHtmlName()
{
return parent::getHtmlName() . '[]';
}
/**
* @return static
*/
public function checkDefaultValue($value = true)
{
$this->checkAllowedValues = $value;
return $this;
}
}
@@ -0,0 +1,86 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms\Controls;
use Nette;
/**
* Select box control that allows multiple items selection.
*/
class MultiSelectBox extends MultiChoiceControl
{
/** @var array of option / optgroup */
private $options = [];
/** @var array */
private $optionAttributes = [];
public function __construct($label = null, array $items = null)
{
parent::__construct($label, $items);
$this->setOption('type', 'select');
}
/**
* Sets options and option groups from which to choose.
* @return static
*/
public function setItems(array $items, $useKeys = true)
{
if (!$useKeys) {
$res = [];
foreach ($items as $key => $value) {
unset($items[$key]);
if (is_array($value)) {
foreach ($value as $val) {
$res[$key][(string) $val] = $val;
}
} else {
$res[(string) $value] = $value;
}
}
$items = $res;
}
$this->options = $items;
return parent::setItems(Nette\Utils\Arrays::flatten($items, true));
}
/**
* Generates control's HTML element.
* @return Nette\Utils\Html
*/
public function getControl()
{
$items = [];
foreach ($this->options as $key => $value) {
$items[is_array($value) ? $this->translate($key) : $key] = $this->translate($value);
}
return Nette\Forms\Helpers::createSelectBox(
$items,
[
'disabled:' => is_array($this->disabled) ? $this->disabled : null,
] + $this->optionAttributes,
$this->value
)->addAttributes(parent::getControl()->attrs)->multiple(true);
}
/**
* @return static
*/
public function addOptionAttributes(array $attributes)
{
$this->optionAttributes = $attributes + $this->optionAttributes;
return $this;
}
}
+147
View File
@@ -0,0 +1,147 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms\Controls;
use Nette;
use Nette\Utils\Html;
/**
* Set of radio button controls.
*
* @property-read Html $separatorPrototype
* @property-read Html $containerPrototype
* @property-read Html $itemLabelPrototype
*/
class RadioList extends ChoiceControl
{
/** @var bool */
public $generateId = false;
/** @var Html separator element template */
protected $separator;
/** @var Html container element template */
protected $container;
/** @var Html item label template */
protected $itemLabel;
/**
* @param string|object
*/
public function __construct($label = null, array $items = null)
{
parent::__construct($label, $items);
$this->control->type = 'radio';
$this->container = Html::el();
$this->separator = Html::el('br');
$this->itemLabel = Html::el('label');
$this->setOption('type', 'radio');
}
/**
* Generates control's HTML element.
* @return Html
*/
public function getControl()
{
$input = parent::getControl();
$items = $this->getItems();
$ids = [];
if ($this->generateId) {
foreach ($items as $value => $label) {
$ids[$value] = $input->id . '-' . $value;
}
}
return $this->container->setHtml(
Nette\Forms\Helpers::createInputList(
$this->translate($items),
array_merge($input->attrs, [
'id:' => $ids,
'checked?' => $this->value,
'disabled:' => $this->disabled,
'data-nette-rules:' => [key($items) => $input->attrs['data-nette-rules']],
]),
['for:' => $ids] + $this->itemLabel->attrs,
$this->separator
)
);
}
/**
* Generates label's HTML element.
* @param string|object
* @return Html
*/
public function getLabel($caption = null)
{
return parent::getLabel($caption)->for(null);
}
/**
* @return Html
*/
public function getControlPart($key = null)
{
$key = key([(string) $key => null]);
return parent::getControl()->addAttributes([
'id' => $this->getHtmlId() . '-' . $key,
'checked' => in_array($key, (array) $this->value, true),
'disabled' => is_array($this->disabled) ? isset($this->disabled[$key]) : $this->disabled,
'value' => $key,
]);
}
/**
* @return Html
*/
public function getLabelPart($key = null)
{
$itemLabel = clone $this->itemLabel;
return func_num_args()
? $itemLabel->setText($this->translate($this->items[$key]))->for($this->getHtmlId() . '-' . $key)
: $this->getLabel();
}
/**
* Returns separator HTML element template.
* @return Html
*/
public function getSeparatorPrototype()
{
return $this->separator;
}
/**
* Returns container HTML element template.
* @return Html
*/
public function getContainerPrototype()
{
return $this->container;
}
/**
* Returns item label HTML element template.
* @return Html
*/
public function getItemLabelPrototype()
{
return $this->itemLabel;
}
}
+129
View File
@@ -0,0 +1,129 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms\Controls;
use Nette;
/**
* Select box control that allows single item selection.
*/
class SelectBox extends ChoiceControl
{
/** validation rule */
const VALID = ':selectBoxValid';
/** @var array of option / optgroup */
private $options = [];
/** @var mixed */
private $prompt = false;
/** @var array */
private $optionAttributes = [];
public function __construct($label = null, array $items = null)
{
parent::__construct($label, $items);
$this->setOption('type', 'select');
$this->addCondition(Nette\Forms\Form::BLANK)
->addRule([$this, 'isOk'], Nette\Forms\Validator::$messages[self::VALID]);
}
/**
* Sets first prompt item in select box.
* @param string|object
* @return static
*/
public function setPrompt($prompt)
{
$this->prompt = $prompt;
return $this;
}
/**
* Returns first prompt item?
* @return mixed
*/
public function getPrompt()
{
return $this->prompt;
}
/**
* Sets options and option groups from which to choose.
* @return static
*/
public function setItems(array $items, $useKeys = true)
{
if (!$useKeys) {
$res = [];
foreach ($items as $key => $value) {
unset($items[$key]);
if (is_array($value)) {
foreach ($value as $val) {
$res[$key][(string) $val] = $val;
}
} else {
$res[(string) $value] = $value;
}
}
$items = $res;
}
$this->options = $items;
return parent::setItems(Nette\Utils\Arrays::flatten($items, true));
}
/**
* Generates control's HTML element.
* @return Nette\Utils\Html
*/
public function getControl()
{
$items = $this->prompt === false ? [] : ['' => $this->translate($this->prompt)];
foreach ($this->options as $key => $value) {
$items[is_array($value) ? $this->translate($key) : $key] = $this->translate($value);
}
return Nette\Forms\Helpers::createSelectBox(
$items,
[
'disabled:' => is_array($this->disabled) ? $this->disabled : null,
] + $this->optionAttributes,
$this->value
)->addAttributes(parent::getControl()->attrs);
}
/**
* @return static
*/
public function addOptionAttributes(array $attributes)
{
$this->optionAttributes = $attributes + $this->optionAttributes;
return $this;
}
/**
* @return bool
*/
public function isOk()
{
return $this->isDisabled()
|| $this->prompt !== false
|| $this->getValue() !== null
|| !$this->options
|| $this->control->size > 1;
}
}
+121
View File
@@ -0,0 +1,121 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms\Controls;
use Nette;
/**
* Submittable button control.
*
* @property-read bool $submittedBy
*/
class SubmitButton extends Button implements Nette\Forms\ISubmitterControl
{
/** @var callable[] function (SubmitButton $sender); Occurs when the button is clicked and form is successfully validated */
public $onClick;
/** @var callable[] function (SubmitButton $sender); Occurs when the button is clicked and form is not validated */
public $onInvalidClick;
/** @var array|null */
private $validationScope;
/**
* @param string|object
*/
public function __construct($caption = null)
{
parent::__construct($caption);
$this->control->type = 'submit';
$this->setOmitted(true);
}
/**
* Loads HTTP data.
* @return void
*/
public function loadHttpData()
{
parent::loadHttpData();
if ($this->isFilled()) {
$this->getForm()->setSubmittedBy($this);
}
}
/**
* Tells if the form was submitted by this button.
* @return bool
*/
public function isSubmittedBy()
{
return $this->getForm()->isSubmitted() === $this;
}
/**
* Sets the validation scope. Clicking the button validates only the controls within the specified scope.
* @return static
*/
public function setValidationScope(/*array*/$scope = null)
{
if ($scope === null || $scope === true) {
$this->validationScope = null;
} else {
$this->validationScope = [];
foreach ($scope ?: [] as $control) {
if (!$control instanceof Nette\Forms\Container && !$control instanceof Nette\Forms\IControl) {
throw new Nette\InvalidArgumentException('Validation scope accepts only Nette\Forms\Container or Nette\Forms\IControl instances.');
}
$this->validationScope[] = $control;
}
}
return $this;
}
/**
* Gets the validation scope.
* @return array|null
*/
public function getValidationScope()
{
return $this->validationScope;
}
/**
* Fires click event.
* @return void
*/
public function click()
{
$this->onClick($this);
}
/**
* Generates control's HTML element.
* @param string|object
* @return Nette\Utils\Html
*/
public function getControl($caption = null)
{
$scope = [];
foreach ((array) $this->validationScope as $control) {
$scope[] = $control->lookupPath(Nette\Forms\Form::class);
}
return parent::getControl($caption)->addAttributes([
'formnovalidate' => $this->validationScope !== null,
'data-nette-validation-scope' => $scope ?: null,
]);
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms\Controls;
use Nette;
/**
* Multiline text input control.
*/
class TextArea extends TextBase
{
/**
* @param string|object
*/
public function __construct($label = null)
{
parent::__construct($label);
$this->control->setName('textarea');
$this->setOption('type', 'textarea');
}
/**
* Generates control's HTML element.
* @return Nette\Utils\Html
*/
public function getControl()
{
return parent::getControl()
->setText($this->getRenderedValue());
}
}
+154
View File
@@ -0,0 +1,154 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms\Controls;
use Nette;
use Nette\Forms\Form;
use Nette\Utils\Strings;
/**
* Implements the basic functionality common to text input controls.
*/
abstract class TextBase extends BaseControl
{
/** @var string */
protected $emptyValue = '';
/** @var mixed unfiltered submitted value */
protected $rawValue = '';
/** @var bool */
private $nullable;
/**
* Sets control's value.
* @return static
* @internal
*/
public function setValue($value)
{
if ($value === null) {
$value = '';
} elseif (!is_scalar($value) && !method_exists($value, '__toString')) {
throw new Nette\InvalidArgumentException(sprintf("Value must be scalar or null, %s given in field '%s'.", gettype($value), $this->name));
}
$this->value = $value;
$this->rawValue = (string) $value;
return $this;
}
/**
* Returns control's value.
* @return mixed
*/
public function getValue()
{
$value = $this->value === Strings::trim($this->translate($this->emptyValue)) ? '' : $this->value;
return $this->nullable && $value === '' ? null : $value;
}
/**
* Sets whether getValue() returns null instead of empty string.
* @param bool
* @return static
*/
public function setNullable($value = true)
{
$this->nullable = (bool) $value;
return $this;
}
/**
* Sets the special value which is treated as empty string.
* @param string
* @return static
*/
public function setEmptyValue($value)
{
$this->emptyValue = (string) $value;
return $this;
}
/**
* Returns the special value which is treated as empty string.
* @return string
*/
public function getEmptyValue()
{
return $this->emptyValue;
}
/**
* Sets the maximum number of allowed characters.
* @param int
* @return static
*/
public function setMaxLength($length)
{
$this->control->maxlength = $length;
return $this;
}
/**
* Appends input string filter callback.
* @param callable
* @return static
*/
public function addFilter($filter)
{
$this->getRules()->addFilter($filter);
return $this;
}
public function getControl()
{
$el = parent::getControl();
if ($this->emptyValue !== '') {
$el->attrs['data-nette-empty-value'] = Strings::trim($this->translate($this->emptyValue));
}
if (isset($el->placeholder)) {
$el->placeholder = $this->translate($el->placeholder);
}
return $el;
}
/**
* @return string|null
*/
protected function getRenderedValue()
{
return $this->rawValue === ''
? ($this->emptyValue === '' ? null : $this->translate($this->emptyValue))
: $this->rawValue;
}
/**
* @return static
*/
public function addRule($validator, $errorMessage = null, $arg = null)
{
if ($validator === Form::LENGTH || $validator === Form::MAX_LENGTH) {
$tmp = is_array($arg) ? $arg[1] : $arg;
if (is_scalar($tmp)) {
$this->control->maxlength = isset($this->control->maxlength) ? min($this->control->maxlength, $tmp) : $tmp;
}
}
return parent::addRule($validator, $errorMessage, $arg);
}
}
+115
View File
@@ -0,0 +1,115 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms\Controls;
use Nette;
use Nette\Forms\Form;
/**
* Single line text input control.
*/
class TextInput extends TextBase
{
/**
* @param string|object
* @param int
*/
public function __construct($label = null, $maxLength = null)
{
parent::__construct($label);
$this->control->maxlength = $maxLength;
$this->setOption('type', 'text');
}
/**
* Loads HTTP data.
* @return void
*/
public function loadHttpData()
{
$this->setValue($this->getHttpData(Form::DATA_LINE));
}
/**
* Changes control's type attribute.
* @param string
* @return static
*/
public function setHtmlType($type)
{
return $this->setType($type);
}
/**
* Alias for setHtmlType()
* @param string
* @return static
*/
public function setType($type)
{
$this->control->type = $type;
return $this;
}
/**
* Generates control's HTML element.
* @return Nette\Utils\Html
*/
public function getControl()
{
return parent::getControl()->addAttributes([
'value' => $this->control->type === 'password' ? $this->control->value : $this->getRenderedValue(),
'type' => $this->control->type ?: 'text',
]);
}
/**
* @return static
*/
public function addRule($validator, $errorMessage = null, $arg = null)
{
if ($this->control->type === null && in_array($validator, [Form::EMAIL, Form::URL, Form::INTEGER], true)) {
static $types = [Form::EMAIL => 'email', Form::URL => 'url', Form::INTEGER => 'number'];
$this->control->type = $types[$validator];
} elseif (
in_array($validator, [Form::MIN, Form::MAX, Form::RANGE], true)
&& in_array($this->control->type, ['number', 'range', 'datetime-local', 'datetime', 'date', 'month', 'week', 'time'], true)
) {
if ($validator === Form::MIN) {
$range = [$arg, null];
} elseif ($validator === Form::MAX) {
$range = [null, $arg];
} else {
$range = $arg;
}
if (isset($range[0]) && is_scalar($range[0])) {
$this->control->min = isset($this->control->min) ? max($this->control->min, $range[0]) : $range[0];
}
if (isset($range[1]) && is_scalar($range[1])) {
$this->control->max = isset($this->control->max) ? min($this->control->max, $range[1]) : $range[1];
}
} elseif (
$validator === Form::PATTERN
&& is_scalar($arg)
&& in_array($this->control->type, [null, 'text', 'search', 'tel', 'url', 'email', 'password'], true)
) {
$this->control->pattern = $arg;
}
return parent::addRule($validator, $errorMessage, $arg);
}
}
+114
View File
@@ -0,0 +1,114 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms\Controls;
use Nette;
use Nette\Forms;
use Nette\Http\FileUpload;
/**
* Text box and browse button that allow users to select a file to upload to the server.
*/
class UploadControl extends BaseControl
{
/** validation rule */
const VALID = ':uploadControlValid';
/**
* @param string|object
* @param bool
*/
public function __construct($label = null, $multiple = false)
{
parent::__construct($label);
$this->control->type = 'file';
$this->control->multiple = (bool) $multiple;
$this->setOption('type', 'file');
$this->addCondition(Forms\Form::FILLED)
->addRule([$this, 'isOk'], Forms\Validator::$messages[self::VALID]);
}
/**
* This method will be called when the component (or component's parent)
* becomes attached to a monitored object. Do not call this method yourself.
* @param Nette\ComponentModel\IComponent
* @return void
*/
protected function attached($form)
{
if ($form instanceof Nette\Forms\Form) {
if (!$form->isMethod('post')) {
throw new Nette\InvalidStateException('File upload requires method POST.');
}
$form->getElementPrototype()->enctype = 'multipart/form-data';
}
parent::attached($form);
}
/**
* Loads HTTP data.
* @return void
*/
public function loadHttpData()
{
$this->value = $this->getHttpData(Nette\Forms\Form::DATA_FILE);
if ($this->value === null) {
$this->value = new FileUpload(null);
}
}
/**
* Returns HTML name of control.
* @return string
*/
public function getHtmlName()
{
return parent::getHtmlName() . ($this->control->multiple ? '[]' : '');
}
/**
* @return static
* @internal
*/
public function setValue($value)
{
return $this;
}
/**
* Has been any file uploaded?
* @return bool
*/
public function isFilled()
{
return $this->value instanceof FileUpload
? $this->value->getError() !== UPLOAD_ERR_NO_FILE // ignore null object
: (bool) $this->value;
}
/**
* Have been all files succesfully uploaded?
* @return bool
*/
public function isOk()
{
return $this->value instanceof FileUpload
? $this->value->isOk()
: $this->value && array_reduce($this->value, function ($carry, $fileUpload) {
return $carry && $fileUpload->isOk();
}, true);
}
}
+725
View File
@@ -0,0 +1,725 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms;
use Nette;
use Nette\Utils\Html;
/**
* Creates, validates and renders HTML forms.
*
* @property-read array $errors
* @property-read array $ownErrors
* @property-read Html $elementPrototype
* @property-read IFormRenderer $renderer
* @property string $action
* @property string $method
*/
class Form extends Container implements Nette\Utils\IHtmlString
{
/** validator */
const
EQUAL = ':equal',
IS_IN = self::EQUAL,
NOT_EQUAL = ':notEqual',
IS_NOT_IN = self::NOT_EQUAL,
FILLED = ':filled',
BLANK = ':blank',
REQUIRED = self::FILLED,
VALID = ':valid',
// button
SUBMITTED = ':submitted',
// text
MIN_LENGTH = ':minLength',
MAX_LENGTH = ':maxLength',
LENGTH = ':length',
EMAIL = ':email',
URL = ':url',
PATTERN = ':pattern',
PATTERN_ICASE = ':patternCaseInsensitive',
INTEGER = ':integer',
NUMERIC = ':integer',
FLOAT = ':float',
MIN = ':min',
MAX = ':max',
RANGE = ':range',
// multiselect
COUNT = self::LENGTH,
// file upload
MAX_FILE_SIZE = ':fileSize',
MIME_TYPE = ':mimeType',
IMAGE = ':image',
MAX_POST_SIZE = ':maxPostSize';
/** @deprecated CSRF protection */
const PROTECTION = Controls\CsrfProtection::PROTECTION;
/** method */
const
GET = 'get',
POST = 'post';
/** submitted data types */
const
DATA_TEXT = 1,
DATA_LINE = 2,
DATA_FILE = 3,
DATA_KEYS = 8;
/** @internal tracker ID */
const TRACKER_ID = '_form_';
/** @internal protection token ID */
const PROTECTOR_ID = '_token_';
/** @var callable[] function (Form $sender); Occurs when the form is submitted and successfully validated */
public $onSuccess;
/** @var callable[] function (Form $sender); Occurs when the form is submitted and is not valid */
public $onError;
/** @var callable[] function (Form $sender); Occurs when the form is submitted */
public $onSubmit;
/** @var callable[] function (Form $sender); Occurs before the form is rendered */
public $onRender;
/** @var Nette\Http\IRequest used only by standalone form */
public $httpRequest;
/** @var mixed or null meaning: not detected yet */
private $submittedBy;
/** @var array */
private $httpData;
/** @var Html <form> element */
private $element;
/** @var IFormRenderer */
private $renderer;
/** @var Nette\Localization\ITranslator */
private $translator;
/** @var ControlGroup[] */
private $groups = [];
/** @var array */
private $errors = [];
/** @var bool */
private $beforeRenderCalled;
/**
* Form constructor.
* @param string
*/
public function __construct($name = null)
{
parent::__construct();
if ($name !== null) {
$this->getElementPrototype()->id = 'frm-' . $name;
$tracker = new Controls\HiddenField($name);
$tracker->setOmitted();
$this[self::TRACKER_ID] = $tracker;
$this->setParent(null, $name);
}
}
/**
* @return void
*/
protected function validateParent(Nette\ComponentModel\IContainer $parent)
{
parent::validateParent($parent);
$this->monitor(__CLASS__);
}
/**
* This method will be called when the component (or component's parent)
* becomes attached to a monitored object. Do not call this method yourself.
* @param Nette\ComponentModel\IComponent
* @return void
*/
protected function attached($obj)
{
if ($obj instanceof self) {
throw new Nette\InvalidStateException('Nested forms are forbidden.');
}
}
/**
* Returns self.
* @return static
*/
public function getForm($throw = true)
{
return $this;
}
/**
* Sets form's action.
* @param string|object
* @return static
*/
public function setAction($url)
{
$this->getElementPrototype()->action = $url;
return $this;
}
/**
* Returns form's action.
* @return mixed
*/
public function getAction()
{
return $this->getElementPrototype()->action;
}
/**
* Sets form's method GET or POST.
* @param string
* @return static
*/
public function setMethod($method)
{
if ($this->httpData !== null) {
throw new Nette\InvalidStateException(__METHOD__ . '() must be called until the form is empty.');
}
$this->getElementPrototype()->method = strtolower($method);
return $this;
}
/**
* Returns form's method.
* @return string
*/
public function getMethod()
{
return $this->getElementPrototype()->method;
}
/**
* Checks if the request method is the given one.
* @param string
* @return bool
*/
public function isMethod($method)
{
return strcasecmp($this->getElementPrototype()->method, $method) === 0;
}
/**
* Cross-Site Request Forgery (CSRF) form protection.
* @param string
* @return Controls\CsrfProtection
*/
public function addProtection($errorMessage = null)
{
$control = new Controls\CsrfProtection($errorMessage);
$this->addComponent($control, self::PROTECTOR_ID, key($this->getComponents()));
return $control;
}
/**
* Adds fieldset group to the form.
* @param string
* @param bool
* @return ControlGroup
*/
public function addGroup($caption = null, $setAsCurrent = true)
{
$group = new ControlGroup;
$group->setOption('label', $caption);
$group->setOption('visual', true);
if ($setAsCurrent) {
$this->setCurrentGroup($group);
}
if (!is_scalar($caption) || isset($this->groups[$caption])) {
return $this->groups[] = $group;
} else {
return $this->groups[$caption] = $group;
}
}
/**
* Removes fieldset group from form.
* @param string|int|ControlGroup
* @return void
*/
public function removeGroup($name)
{
if (is_string($name) && isset($this->groups[$name])) {
$group = $this->groups[$name];
} elseif ($name instanceof ControlGroup && in_array($name, $this->groups, true)) {
$group = $name;
$name = array_search($group, $this->groups, true);
} else {
throw new Nette\InvalidArgumentException("Group not found in form '$this->name'");
}
foreach ($group->getControls() as $control) {
$control->getParent()->removeComponent($control);
}
unset($this->groups[$name]);
}
/**
* Returns all defined groups.
* @return ControlGroup[]
*/
public function getGroups()
{
return $this->groups;
}
/**
* Returns the specified group.
* @param string|int
* @return ControlGroup|null
*/
public function getGroup($name)
{
return isset($this->groups[$name]) ? $this->groups[$name] : null;
}
/********************* translator ****************d*g**/
/**
* Sets translate adapter.
* @return static
*/
public function setTranslator(Nette\Localization\ITranslator $translator = null)
{
$this->translator = $translator;
return $this;
}
/**
* Returns translate adapter.
* @return Nette\Localization\ITranslator|null
*/
public function getTranslator()
{
return $this->translator;
}
/********************* submission ****************d*g**/
/**
* Tells if the form is anchored.
* @return bool
*/
public function isAnchored()
{
return true;
}
/**
* Tells if the form was submitted.
* @return ISubmitterControl|bool submittor control
*/
public function isSubmitted()
{
if ($this->submittedBy === null) {
$this->getHttpData();
}
return $this->submittedBy;
}
/**
* Tells if the form was submitted and successfully validated.
* @return bool
*/
public function isSuccess()
{
return $this->isSubmitted() && $this->isValid();
}
/**
* Sets the submittor control.
* @return static
* @internal
*/
public function setSubmittedBy(ISubmitterControl $by = null)
{
$this->submittedBy = $by === null ? false : $by;
return $this;
}
/**
* Returns submitted HTTP data.
* @param int
* @param string
* @return mixed
*/
public function getHttpData($type = null, $htmlName = null)
{
if ($this->httpData === null) {
if (!$this->isAnchored()) {
throw new Nette\InvalidStateException('Form is not anchored and therefore can not determine whether it was submitted.');
}
$data = $this->receiveHttpData();
$this->httpData = (array) $data;
$this->submittedBy = is_array($data);
}
if ($htmlName === null) {
return $this->httpData;
}
return Helpers::extractHttpData($this->httpData, $htmlName, $type);
}
/**
* Fires submit/click events.
* @return void
*/
public function fireEvents()
{
if (!$this->isSubmitted()) {
return;
} elseif (!$this->getErrors()) {
$this->validate();
}
if ($this->submittedBy instanceof ISubmitterControl) {
if ($this->isValid()) {
if ($handlers = $this->submittedBy->onClick) {
if (!is_array($handlers) && !$handlers instanceof \Traversable) {
throw new Nette\UnexpectedValueException("Property \$onClick in button '{$this->submittedBy->getName()}' must be iterable, " . gettype($handlers) . ' given.');
}
$this->invokeHandlers($handlers, $this->submittedBy);
}
} else {
$this->submittedBy->onInvalidClick($this->submittedBy);
}
}
if (!$this->isValid()) {
$this->onError($this);
} elseif ($this->onSuccess !== null) {
if (!is_array($this->onSuccess) && !$this->onSuccess instanceof \Traversable) {
throw new Nette\UnexpectedValueException('Property Form::$onSuccess must be array or Traversable, ' . gettype($this->onSuccess) . ' given.');
}
$this->invokeHandlers($this->onSuccess);
if (!$this->isValid()) {
$this->onError($this);
}
}
$this->onSubmit($this);
}
private function invokeHandlers($handlers, $button = null)
{
foreach ($handlers as $handler) {
$params = Nette\Utils\Callback::toReflection($handler)->getParameters();
$values = isset($params[1]) ? $this->getValues($params[1]->isArray()) : null;
Nette\Utils\Callback::invoke($handler, $button ?: $this, $values);
if (!$button && !$this->isValid()) {
return;
}
}
}
/**
* Resets form.
* @return static
*/
public function reset()
{
$this->setSubmittedBy(null);
$this->setValues([], true);
return $this;
}
/**
* Internal: returns submitted HTTP data or null when form was not submitted.
* @return array|null
*/
protected function receiveHttpData()
{
$httpRequest = $this->getHttpRequest();
if (strcasecmp($this->getMethod(), $httpRequest->getMethod())) {
return;
}
if ($httpRequest->isMethod('post')) {
$data = Nette\Utils\Arrays::mergeTree($httpRequest->getPost(), $httpRequest->getFiles());
} else {
$data = $httpRequest->getQuery();
if (!$data) {
return;
}
}
if ($tracker = $this->getComponent(self::TRACKER_ID, false)) {
if (!isset($data[self::TRACKER_ID]) || $data[self::TRACKER_ID] !== $tracker->getValue()) {
return;
}
}
return $data;
}
/********************* validation ****************d*g**/
/**
* @return void
*/
public function validate(array $controls = null)
{
$this->cleanErrors();
if ($controls === null && $this->submittedBy instanceof ISubmitterControl) {
$controls = $this->submittedBy->getValidationScope();
}
$this->validateMaxPostSize();
parent::validate($controls);
}
/** @internal */
public function validateMaxPostSize()
{
if (!$this->submittedBy || !$this->isMethod('post') || empty($_SERVER['CONTENT_LENGTH'])) {
return;
}
$maxSize = ini_get('post_max_size');
$units = ['k' => 10, 'm' => 20, 'g' => 30];
if (isset($units[$ch = strtolower(substr($maxSize, -1))])) {
$maxSize = (int) $maxSize << $units[$ch];
}
if ($maxSize > 0 && $maxSize < $_SERVER['CONTENT_LENGTH']) {
$this->addError(sprintf(Validator::$messages[self::MAX_FILE_SIZE], $maxSize));
}
}
/**
* Adds global error message.
* @param string|object
* @return void
*/
public function addError($message, $translate = true)
{
if ($translate && $this->translator) {
$message = $this->translator->translate($message);
}
$this->errors[] = $message;
}
/**
* Returns global validation errors.
* @return array
*/
public function getErrors()
{
return array_unique(array_merge($this->errors, parent::getErrors()));
}
/**
* @return bool
*/
public function hasErrors()
{
return (bool) $this->getErrors();
}
/**
* @return void
*/
public function cleanErrors()
{
$this->errors = [];
}
/**
* Returns form's validation errors.
* @return array
*/
public function getOwnErrors()
{
return array_unique($this->errors);
}
/********************* rendering ****************d*g**/
/**
* Returns form's HTML element template.
* @return Html
*/
public function getElementPrototype()
{
if (!$this->element) {
$this->element = Html::el('form');
$this->element->action = ''; // RFC 1808 -> empty uri means 'this'
$this->element->method = self::POST;
}
return $this->element;
}
/**
* Sets form renderer.
* @return static
*/
public function setRenderer(IFormRenderer $renderer = null)
{
$this->renderer = $renderer;
return $this;
}
/**
* Returns form renderer.
* @return IFormRenderer
*/
public function getRenderer()
{
if ($this->renderer === null) {
$this->renderer = new Rendering\DefaultFormRenderer;
}
return $this->renderer;
}
/**
* @return void
*/
protected function beforeRender()
{
}
/**
* Must be called before form is rendered and render() is not used.
* @return void
*/
public function fireRenderEvents()
{
if (!$this->beforeRenderCalled) {
foreach ($this->getComponents(true, Controls\BaseControl::class) as $control) {
$control->getRules()->check();
}
$this->beforeRenderCalled = true;
$this->beforeRender();
$this->onRender($this);
}
}
/**
* Renders form.
* @return void
*/
public function render(...$args)
{
$this->fireRenderEvents();
echo $this->getRenderer()->render($this, ...$args);
}
/**
* Renders form to string.
* @param can throw exceptions? (hidden parameter)
* @return string
*/
public function __toString()
{
try {
$this->fireRenderEvents();
return $this->getRenderer()->render($this);
} catch (\Exception $e) {
} catch (\Throwable $e) {
}
if (isset($e)) {
if (func_num_args()) {
throw $e;
}
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
}
}
/********************* backend ****************d*g**/
/**
* @return Nette\Http\IRequest
*/
private function getHttpRequest()
{
if (!$this->httpRequest) {
$factory = new Nette\Http\RequestFactory;
$this->httpRequest = $factory->createHttpRequest();
}
return $this->httpRequest;
}
/**
* @return array
*/
public function getToggles()
{
$toggles = [];
foreach ($this->getComponents(true, Controls\BaseControl::class) as $control) {
$toggles = $control->getRules()->getToggleStates($toggles);
}
return $toggles;
}
}
+233
View File
@@ -0,0 +1,233 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms;
use Nette;
use Nette\Utils\Html;
use Nette\Utils\Strings;
/**
* Forms helpers.
*/
class Helpers
{
use Nette\StaticClass;
private static $unsafeNames = [
'attributes', 'children', 'elements', 'focus', 'length', 'reset', 'style', 'submit', 'onsubmit', 'form',
'presenter', 'action',
];
/**
* Extracts and sanitizes submitted form data for single control.
* @param array
* @param string
* @param int type Form::DATA_TEXT, DATA_LINE, DATA_FILE, DATA_KEYS
* @return string|string[]
* @internal
*/
public static function extractHttpData(array $data, $htmlName, $type)
{
$name = explode('[', str_replace(['[]', ']', '.'], ['', '', '_'], $htmlName));
$data = Nette\Utils\Arrays::get($data, $name, null);
$itype = $type & ~Form::DATA_KEYS;
if (substr($htmlName, -2) === '[]') {
if (!is_array($data)) {
return [];
}
foreach ($data as $k => $v) {
$data[$k] = $v = static::sanitize($itype, $v);
if ($v === null) {
unset($data[$k]);
}
}
if ($type & Form::DATA_KEYS) {
return $data;
}
return array_values($data);
} else {
return static::sanitize($itype, $data);
}
}
private static function sanitize($type, $value)
{
if ($type === Form::DATA_TEXT) {
return is_scalar($value) ? Strings::normalizeNewLines($value) : null;
} elseif ($type === Form::DATA_LINE) {
return is_scalar($value) ? Strings::trim(strtr((string) $value, "\r\n", ' ')) : null;
} elseif ($type === Form::DATA_FILE) {
return $value instanceof Nette\Http\FileUpload ? $value : null;
} else {
throw new Nette\InvalidArgumentException('Unknown data type');
}
}
/**
* Converts control name to HTML name.
* @return string
*/
public static function generateHtmlName($id)
{
$name = str_replace(Nette\ComponentModel\IComponent::NAME_SEPARATOR, '][', $id, $count);
if ($count) {
$name = substr_replace($name, '', strpos($name, ']'), 1) . ']';
}
if (is_numeric($name) || in_array($name, self::$unsafeNames, true)) {
$name = '_' . $name;
}
return $name;
}
/**
* @return array
*/
public static function exportRules(Rules $rules)
{
$payload = [];
foreach ($rules as $rule) {
if (!is_string($op = $rule->validator)) {
if (!Nette\Utils\Callback::isStatic($op)) {
continue;
}
$op = Nette\Utils\Callback::toString($op);
}
if ($rule->branch) {
$item = [
'op' => ($rule->isNegative ? '~' : '') . $op,
'rules' => static::exportRules($rule->branch),
'control' => $rule->control->getHtmlName(),
];
if ($rule->branch->getToggles()) {
$item['toggle'] = $rule->branch->getToggles();
} elseif (!$item['rules']) {
continue;
}
} else {
$item = ['op' => ($rule->isNegative ? '~' : '') . $op, 'msg' => Validator::formatMessage($rule, false)];
}
if (is_array($rule->arg)) {
$item['arg'] = [];
foreach ($rule->arg as $key => $value) {
$item['arg'][$key] = $value instanceof IControl ? ['control' => $value->getHtmlName()] : $value;
}
} elseif ($rule->arg !== null) {
$item['arg'] = $rule->arg instanceof IControl ? ['control' => $rule->arg->getHtmlName()] : $rule->arg;
}
$payload[] = $item;
}
if ($payload && $rules->isOptional()) {
array_unshift($payload, ['op' => 'optional']);
}
return $payload;
}
/**
* @return string
*/
public static function createInputList(array $items, array $inputAttrs = null, array $labelAttrs = null, $wrapper = null)
{
list($inputAttrs, $inputTag) = self::prepareAttrs($inputAttrs, 'input');
list($labelAttrs, $labelTag) = self::prepareAttrs($labelAttrs, 'label');
$res = '';
$input = Html::el();
$label = Html::el();
list($wrapper, $wrapperEnd) = $wrapper instanceof Html ? [$wrapper->startTag(), $wrapper->endTag()] : [(string) $wrapper, ''];
foreach ($items as $value => $caption) {
foreach ($inputAttrs as $k => $v) {
$input->attrs[$k] = isset($v[$value]) ? $v[$value] : null;
}
foreach ($labelAttrs as $k => $v) {
$label->attrs[$k] = isset($v[$value]) ? $v[$value] : null;
}
$input->value = $value;
$res .= ($res === '' && $wrapperEnd === '' ? '' : $wrapper)
. $labelTag . $label->attributes() . '>'
. $inputTag . $input->attributes() . (Html::$xhtml ? ' />' : '>')
. ($caption instanceof Nette\Utils\IHtmlString ? $caption : htmlspecialchars($caption, ENT_NOQUOTES, 'UTF-8'))
. '</label>'
. $wrapperEnd;
}
return $res;
}
/**
* @return Html
*/
public static function createSelectBox(array $items, array $optionAttrs = null, $selected = null)
{
if ($selected !== null) {
$optionAttrs['selected?'] = $selected;
}
list($optionAttrs, $optionTag) = self::prepareAttrs($optionAttrs, 'option');
$option = Html::el();
$res = $tmp = '';
foreach ($items as $group => $subitems) {
if (is_array($subitems)) {
$res .= Html::el('optgroup')->label($group)->startTag();
$tmp = '</optgroup>';
} else {
$subitems = [$group => $subitems];
}
foreach ($subitems as $value => $caption) {
$option->value = $value;
foreach ($optionAttrs as $k => $v) {
$option->attrs[$k] = isset($v[$value]) ? $v[$value] : null;
}
if ($caption instanceof Html) {
$caption = clone $caption;
$res .= $caption->setName('option')->addAttributes($option->attrs);
} else {
$res .= $optionTag . $option->attributes() . '>'
. htmlspecialchars((string) $caption, ENT_NOQUOTES, 'UTF-8')
. '</option>';
}
if ($selected === $value) {
unset($optionAttrs['selected'], $option->attrs['selected']);
}
}
$res .= $tmp;
$tmp = '';
}
return Html::el('select')->setHtml($res);
}
private static function prepareAttrs($attrs, $name)
{
$dynamic = [];
foreach ((array) $attrs as $k => $v) {
$p = str_split($k, strlen($k) - 1);
if ($p[1] === '?' || $p[1] === ':') {
unset($attrs[$k], $attrs[$p[0]]);
if ($p[1] === '?') {
$dynamic[$p[0]] = array_fill_keys((array) $v, true);
} elseif (is_array($v) && $v) {
$dynamic[$p[0]] = $v;
} else {
$attrs[$p[0]] = $v;
}
}
}
return [$dynamic, '<' . $name . Html::el(null, $attrs)->attributes()];
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms;
/**
* Defines method that must be implemented to allow a component to act like a form control.
*/
interface IControl
{
/**
* Sets control's value.
* @param mixed
* @return static
*/
function setValue($value);
/**
* Returns control's value.
* @return mixed
*/
function getValue();
/**
* @return void
*/
function validate();
/**
* Returns errors corresponding to control.
* @return array
*/
function getErrors();
/**
* Is control value excluded from $form->getValues() result?
* @return bool
*/
function isOmitted();
}
+22
View File
@@ -0,0 +1,22 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms;
/**
* Defines method that must implement form renderer.
*/
interface IFormRenderer
{
/**
* Provides complete form rendering.
* @return string
*/
function render(Form $form);
}
+22
View File
@@ -0,0 +1,22 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms;
/**
* Defines method that must be implemented to allow a control to submit web form.
*/
interface ISubmitterControl extends IControl
{
/**
* Gets the validation scope. Clicking the button validates only the controls within the specified scope.
* @return array|null
*/
function getValidationScope();
}
@@ -0,0 +1,487 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms\Rendering;
use Nette;
use Nette\Utils\Html;
use Nette\Utils\IHtmlString;
/**
* Converts a Form into the HTML output.
*/
class DefaultFormRenderer implements Nette\Forms\IFormRenderer
{
use Nette\SmartObject;
/**
* /--- form.container
*
* /--- error.container
* .... error.item [.class]
* \---
*
* /--- hidden.container
* .... HIDDEN CONTROLS
* \---
*
* /--- group.container
* .... group.label
* .... group.description
*
* /--- controls.container
*
* /--- pair.container [.required .optional .odd]
*
* /--- label.container
* .... LABEL
* .... label.suffix
* .... label.requiredsuffix
* \---
*
* /--- control.container [.odd]
* .... CONTROL [.required .text .password .file .submit .button]
* .... control.requiredsuffix
* .... control.description
* .... control.errorcontainer + control.erroritem
* \---
* \---
* \---
* \---
* \--
* @var array of HTML tags */
public $wrappers = [
'form' => [
'container' => null,
],
'error' => [
'container' => 'ul class=error',
'item' => 'li',
],
'group' => [
'container' => 'fieldset',
'label' => 'legend',
'description' => 'p',
],
'controls' => [
'container' => 'table',
],
'pair' => [
'container' => 'tr',
'.required' => 'required',
'.optional' => null,
'.odd' => null,
'.error' => null,
],
'control' => [
'container' => 'td',
'.odd' => null,
'description' => 'small',
'requiredsuffix' => '',
'errorcontainer' => 'span class=error',
'erroritem' => '',
'.required' => 'required',
'.text' => 'text',
'.password' => 'text',
'.file' => 'text',
'.email' => 'text',
'.number' => 'text',
'.submit' => 'button',
'.image' => 'imagebutton',
'.button' => 'button',
],
'label' => [
'container' => 'th',
'suffix' => null,
'requiredsuffix' => '',
],
'hidden' => [
'container' => null,
],
];
/** @var Nette\Forms\Form */
protected $form;
/** @var int */
protected $counter;
/**
* Provides complete form rendering.
* @param Nette\Forms\Form
* @param string 'begin', 'errors', 'ownerrors', 'body', 'end' or empty to render all
* @return string
*/
public function render(Nette\Forms\Form $form, $mode = null)
{
if ($this->form !== $form) {
$this->form = $form;
}
$s = '';
if (!$mode || $mode === 'begin') {
$s .= $this->renderBegin();
}
if (!$mode || strtolower($mode) === 'ownerrors') {
$s .= $this->renderErrors();
} elseif ($mode === 'errors') {
$s .= $this->renderErrors(null, false);
}
if (!$mode || $mode === 'body') {
$s .= $this->renderBody();
}
if (!$mode || $mode === 'end') {
$s .= $this->renderEnd();
}
return $s;
}
/**
* Renders form begin.
* @return string
*/
public function renderBegin()
{
$this->counter = 0;
foreach ($this->form->getControls() as $control) {
$control->setOption('rendered', false);
}
if ($this->form->isMethod('get')) {
$el = clone $this->form->getElementPrototype();
$query = parse_url($el->action, PHP_URL_QUERY);
$el->action = str_replace("?$query", '', $el->action);
$s = '';
foreach (preg_split('#[;&]#', $query, -1, PREG_SPLIT_NO_EMPTY) as $param) {
$parts = explode('=', $param, 2);
$name = urldecode($parts[0]);
$prefix = explode('[', $name, 2)[0];
if (!isset($this->form[$prefix])) {
$s .= Html::el('input', ['type' => 'hidden', 'name' => $name, 'value' => urldecode($parts[1])]);
}
}
return $el->startTag() . ($s ? "\n\t" . $this->getWrapper('hidden container')->setHtml($s) : '');
} else {
return $this->form->getElementPrototype()->startTag();
}
}
/**
* Renders form end.
* @return string
*/
public function renderEnd()
{
$s = '';
foreach ($this->form->getControls() as $control) {
if ($control->getOption('type') === 'hidden' && !$control->getOption('rendered')) {
$s .= $control->getControl();
}
}
if (iterator_count($this->form->getComponents(true, Nette\Forms\Controls\TextInput::class)) < 2) {
$s .= '<!--[if IE]><input type=IEbug disabled style="display:none"><![endif]-->';
}
if ($s) {
$s = $this->getWrapper('hidden container')->setHtml($s) . "\n";
}
return $s . $this->form->getElementPrototype()->endTag() . "\n";
}
/**
* Renders validation errors (per form or per control).
* @return string
*/
public function renderErrors(Nette\Forms\IControl $control = null, $own = true)
{
$errors = $control
? $control->getErrors()
: ($own ? $this->form->getOwnErrors() : $this->form->getErrors());
if (!$errors) {
return '';
}
$container = $this->getWrapper($control ? 'control errorcontainer' : 'error container');
$item = $this->getWrapper($control ? 'control erroritem' : 'error item');
foreach ($errors as $error) {
$item = clone $item;
if ($error instanceof IHtmlString) {
$item->addHtml($error);
} else {
$item->setText($error);
}
$container->addHtml($item);
}
return "\n" . $container->render($control ? 1 : 0);
}
/**
* Renders form body.
* @return string
*/
public function renderBody()
{
$s = $remains = '';
$defaultContainer = $this->getWrapper('group container');
$translator = $this->form->getTranslator();
foreach ($this->form->getGroups() as $group) {
if (!$group->getControls() || !$group->getOption('visual')) {
continue;
}
$container = $group->getOption('container', $defaultContainer);
$container = $container instanceof Html ? clone $container : Html::el($container);
$id = $group->getOption('id');
if ($id) {
$container->id = $id;
}
$s .= "\n" . $container->startTag();
$text = $group->getOption('label');
if ($text instanceof IHtmlString) {
$s .= $this->getWrapper('group label')->addHtml($text);
} elseif ($text != null) { // intentionally ==
if ($translator !== null) {
$text = $translator->translate($text);
}
$s .= "\n" . $this->getWrapper('group label')->setText($text) . "\n";
}
$text = $group->getOption('description');
if ($text instanceof IHtmlString) {
$s .= $text;
} elseif ($text != null) { // intentionally ==
if ($translator !== null) {
$text = $translator->translate($text);
}
$s .= $this->getWrapper('group description')->setText($text) . "\n";
}
$s .= $this->renderControls($group);
$remains = $container->endTag() . "\n" . $remains;
if (!$group->getOption('embedNext')) {
$s .= $remains;
$remains = '';
}
}
$s .= $remains . $this->renderControls($this->form);
$container = $this->getWrapper('form container');
$container->setHtml($s);
return $container->render(0);
}
/**
* Renders group of controls.
* @param Nette\Forms\Container|Nette\Forms\ControlGroup
* @return string
*/
public function renderControls($parent)
{
if (!($parent instanceof Nette\Forms\Container || $parent instanceof Nette\Forms\ControlGroup)) {
throw new Nette\InvalidArgumentException('Argument must be Nette\Forms\Container or Nette\Forms\ControlGroup instance.');
}
$container = $this->getWrapper('controls container');
$buttons = null;
foreach ($parent->getControls() as $control) {
if ($control->getOption('rendered') || $control->getOption('type') === 'hidden' || $control->getForm(false) !== $this->form) {
// skip
} elseif ($control->getOption('type') === 'button') {
$buttons[] = $control;
} else {
if ($buttons) {
$container->addHtml($this->renderPairMulti($buttons));
$buttons = null;
}
$container->addHtml($this->renderPair($control));
}
}
if ($buttons) {
$container->addHtml($this->renderPairMulti($buttons));
}
$s = '';
if (count($container)) {
$s .= "\n" . $container . "\n";
}
return $s;
}
/**
* Renders single visual row.
* @return string
*/
public function renderPair(Nette\Forms\IControl $control)
{
$pair = $this->getWrapper('pair container');
$pair->addHtml($this->renderLabel($control));
$pair->addHtml($this->renderControl($control));
$pair->class($this->getValue($control->isRequired() ? 'pair .required' : 'pair .optional'), true);
$pair->class($control->hasErrors() ? $this->getValue('pair .error') : null, true);
$pair->class($control->getOption('class'), true);
if (++$this->counter % 2) {
$pair->class($this->getValue('pair .odd'), true);
}
$pair->id = $control->getOption('id');
return $pair->render(0);
}
/**
* Renders single visual row of multiple controls.
* @param Nette\Forms\IControl[]
* @return string
*/
public function renderPairMulti(array $controls)
{
$s = [];
foreach ($controls as $control) {
if (!$control instanceof Nette\Forms\IControl) {
throw new Nette\InvalidArgumentException('Argument must be array of Nette\Forms\IControl instances.');
}
$description = $control->getOption('description');
if ($description instanceof IHtmlString) {
$description = ' ' . $description;
} elseif ($description != null) { // intentionally ==
if ($control instanceof Nette\Forms\Controls\BaseControl) {
$description = $control->translate($description);
}
$description = ' ' . $this->getWrapper('control description')->setText($description);
} else {
$description = '';
}
$control->setOption('rendered', true);
$el = $control->getControl();
if ($el instanceof Html && $el->getName() === 'input') {
$el->class($this->getValue("control .$el->type"), true);
}
$s[] = $el . $description;
}
$pair = $this->getWrapper('pair container');
$pair->addHtml($this->renderLabel($control));
$pair->addHtml($this->getWrapper('control container')->setHtml(implode(' ', $s)));
return $pair->render(0);
}
/**
* Renders 'label' part of visual row of controls.
* @return Html
*/
public function renderLabel(Nette\Forms\IControl $control)
{
$suffix = $this->getValue('label suffix') . ($control->isRequired() ? $this->getValue('label requiredsuffix') : '');
$label = $control->getLabel();
if ($label instanceof Html) {
$label->addHtml($suffix);
if ($control->isRequired()) {
$label->class($this->getValue('control .required'), true);
}
} elseif ($label != null) { // @intentionally ==
$label .= $suffix;
}
return $this->getWrapper('label container')->setHtml($label);
}
/**
* Renders 'control' part of visual row of controls.
* @return Html
*/
public function renderControl(Nette\Forms\IControl $control)
{
$body = $this->getWrapper('control container');
if ($this->counter % 2) {
$body->class($this->getValue('control .odd'), true);
}
$description = $control->getOption('description');
if ($description instanceof IHtmlString) {
$description = ' ' . $description;
} elseif ($description != null) { // intentionally ==
if ($control instanceof Nette\Forms\Controls\BaseControl) {
$description = $control->translate($description);
}
$description = ' ' . $this->getWrapper('control description')->setText($description);
} else {
$description = '';
}
if ($control->isRequired()) {
$description = $this->getValue('control requiredsuffix') . $description;
}
$control->setOption('rendered', true);
$el = $control->getControl();
if ($el instanceof Html && $el->getName() === 'input') {
$el->class($this->getValue("control .$el->type"), true);
}
return $body->setHtml($el . $description . $this->renderErrors($control));
}
/**
* @param string
* @return Html
*/
protected function getWrapper($name)
{
$data = $this->getValue($name);
return $data instanceof Html ? clone $data : Html::el($data);
}
/**
* @param string
* @return mixed
*/
protected function getValue($name)
{
$name = explode(' ', $name);
$data = &$this->wrappers[$name[0]][$name[1]];
return $data;
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms;
use Nette;
/**
* Single validation rule or condition represented as value object.
*/
class Rule
{
use Nette\SmartObject;
/** @var IControl */
public $control;
/** @var mixed */
public $validator;
/** @var mixed */
public $arg;
/** @var bool */
public $isNegative = false;
/** @var string */
public $message;
/** @var Rules for conditions */
public $branch;
}
+356
View File
@@ -0,0 +1,356 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms;
use Nette;
/**
* List of validation & condition rules.
*/
class Rules implements \IteratorAggregate
{
use Nette\SmartObject;
/** @deprecated */
public static $defaultMessages;
/** @var Rule|false|null */
private $required;
/** @var Rule[] */
private $rules = [];
/** @var Rules */
private $parent;
/** @var array */
private $toggles = [];
/** @var IControl */
private $control;
public function __construct(IControl $control)
{
$this->control = $control;
}
/**
* Makes control mandatory.
* @param mixed state or error message
* @return static
*/
public function setRequired($value = true)
{
if ($value) {
$this->addRule(Form::REQUIRED, $value === true ? null : $value);
} else {
$this->required = false;
}
return $this;
}
/**
* Is control mandatory?
* @return bool
*/
public function isRequired()
{
return (bool) $this->required;
}
/**
* @internal
*/
public function isOptional()
{
return $this->required === false;
}
/**
* Adds a validation rule for the current control.
* @param mixed
* @param string|object
* @param mixed
* @return static
*/
public function addRule($validator, $errorMessage = null, $arg = null)
{
if ($validator === Form::VALID || $validator === ~Form::VALID) {
throw new Nette\InvalidArgumentException('You cannot use Form::VALID in the addRule method.');
}
$rule = new Rule;
$rule->control = $this->control;
$rule->validator = $validator;
$this->adjustOperation($rule);
$rule->arg = $arg;
$rule->message = $errorMessage;
if ($rule->validator === Form::REQUIRED) {
$this->required = $rule;
} else {
$this->rules[] = $rule;
}
return $this;
}
/**
* Adds a validation condition and returns new branch.
* @param mixed
* @param mixed
* @return static new branch
*/
public function addCondition($validator, $arg = null)
{
if ($validator === Form::VALID || $validator === ~Form::VALID) {
throw new Nette\InvalidArgumentException('You cannot use Form::VALID in the addCondition method.');
} elseif (is_bool($validator)) {
$arg = $validator;
$validator = ':static';
}
return $this->addConditionOn($this->control, $validator, $arg);
}
/**
* Adds a validation condition on specified control a returns new branch.
* @param IControl
* @param mixed
* @param mixed
* @return static new branch
*/
public function addConditionOn(IControl $control, $validator, $arg = null)
{
$rule = new Rule;
$rule->control = $control;
$rule->validator = $validator;
$rule->arg = $arg;
$rule->branch = new static($this->control);
$rule->branch->parent = $this;
$this->adjustOperation($rule);
$this->rules[] = $rule;
return $rule->branch;
}
/**
* Adds a else statement.
* @return static else branch
*/
public function elseCondition()
{
$rule = clone end($this->parent->rules);
$rule->isNegative = !$rule->isNegative;
$rule->branch = new static($this->parent->control);
$rule->branch->parent = $this->parent;
$this->parent->rules[] = $rule;
return $rule->branch;
}
/**
* Ends current validation condition.
* @return Rules parent branch
*/
public function endCondition()
{
return $this->parent;
}
/**
* Adds a filter callback.
* @param callable
* @return static
*/
public function addFilter($filter)
{
Nette\Utils\Callback::check($filter);
$this->rules[] = $rule = new Rule;
$rule->control = $this->control;
$rule->validator = function (IControl $control) use ($filter) {
$control->setValue(call_user_func($filter, $control->getValue()));
return true;
};
return $this;
}
/**
* Toggles HTML element visibility.
* @param string
* @param bool
* @return static
*/
public function toggle($id, $hide = true)
{
$this->toggles[$id] = $hide;
return $this;
}
/**
* @param bool
* @return array
*/
public function getToggles($actual = false)
{
return $actual ? $this->getToggleStates() : $this->toggles;
}
/**
* @internal
* @return array
*/
public function getToggleStates($toggles = [], $success = true)
{
foreach ($this->toggles as $id => $hide) {
$toggles[$id] = ($success xor !$hide) || !empty($toggles[$id]);
}
foreach ($this->rules as $rule) {
if ($rule->branch) {
$toggles = $rule->branch->getToggleStates($toggles, $success && static::validateRule($rule));
}
}
return $toggles;
}
/**
* Validates against ruleset.
* @return bool
*/
public function validate($emptyOptional = false)
{
$emptyOptional = $emptyOptional || $this->isOptional() && !$this->control->isFilled();
foreach ($this as $rule) {
if (!$rule->branch && $emptyOptional && $rule->validator !== Form::FILLED) {
continue;
}
$success = $this->validateRule($rule);
if ($success && $rule->branch && !$rule->branch->validate($rule->validator === Form::BLANK ? false : $emptyOptional)) {
return false;
} elseif (!$success && !$rule->branch) {
$rule->control->addError(Validator::formatMessage($rule, true), false);
return false;
}
}
return true;
}
/**
* @internal
*/
public function check()
{
if ($this->required !== null) {
return;
}
foreach ($this->rules as $rule) {
if ($rule->control === $this->control && ($rule->validator === Form::FILLED || $rule->validator === Form::BLANK)) {
// ignore
} elseif ($rule->branch) {
if ($rule->branch->check() === true) {
return true;
}
} else {
trigger_error("Missing setRequired(true | false) on field '{$rule->control->getName()}' in form '{$rule->control->getForm()->getName()}'.", E_USER_WARNING);
return true;
}
}
}
/**
* Clear all validation rules.
*/
public function reset()
{
$this->rules = [];
}
/**
* Validates single rule.
* @return bool
*/
public static function validateRule(Rule $rule)
{
$args = is_array($rule->arg) ? $rule->arg : [$rule->arg];
foreach ($args as &$val) {
$val = $val instanceof IControl ? $val->getValue() : $val;
}
return $rule->isNegative
xor call_user_func(self::getCallback($rule), $rule->control, is_array($rule->arg) ? $args : $args[0]);
}
/**
* Iterates over complete ruleset.
* @return \Iterator
*/
public function getIterator()
{
$rules = $this->rules;
if ($this->required) {
array_unshift($rules, $this->required);
}
return new \ArrayIterator($rules);
}
/**
* Process 'operation' string.
* @return void
*/
private function adjustOperation(Rule $rule)
{
if (is_string($rule->validator) && ord($rule->validator[0]) > 127) {
$rule->isNegative = true;
$rule->validator = ~$rule->validator;
if (!$rule->branch) {
$name = strncmp($rule->validator, ':', 1) ? $rule->validator : 'Form:' . strtoupper($rule->validator);
trigger_error("Negative validation rules such as ~$name are deprecated.", E_USER_DEPRECATED);
}
if ($rule->validator === Form::FILLED) {
$rule->validator = Form::BLANK;
$rule->isNegative = false;
trigger_error('Replace negative validation rule ~Form::FILLED with Form::BLANK.', E_USER_DEPRECATED);
}
}
if (!is_callable($this->getCallback($rule))) {
$validator = is_scalar($rule->validator) ? " '$rule->validator'" : '';
throw new Nette\InvalidArgumentException("Unknown validator$validator for control '{$rule->control->name}'.");
}
}
private static function getCallback(Rule $rule)
{
$op = $rule->validator;
if (is_string($op) && strncmp($op, ':', 1) === 0) {
return 'Nette\Forms\Validator::validate' . ltrim($op, ':');
} else {
return $op;
}
}
}
Rules::$defaultMessages = &Validator::$messages;
+379
View File
@@ -0,0 +1,379 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Forms;
use Nette;
use Nette\Utils\Strings;
use Nette\Utils\Validators;
/**
* Common validators.
*/
class Validator
{
use Nette\StaticClass;
/** @var array */
public static $messages = [
Form::PROTECTION => 'Your session has expired. Please return to the home page and try again.',
Form::EQUAL => 'Please enter %s.',
Form::NOT_EQUAL => 'This value should not be %s.',
Form::FILLED => 'This field is required.',
Form::BLANK => 'This field should be blank.',
Form::MIN_LENGTH => 'Please enter at least %d characters.',
Form::MAX_LENGTH => 'Please enter no more than %d characters.',
Form::LENGTH => 'Please enter a value between %d and %d characters long.',
Form::EMAIL => 'Please enter a valid email address.',
Form::URL => 'Please enter a valid URL.',
Form::INTEGER => 'Please enter a valid integer.',
Form::FLOAT => 'Please enter a valid number.',
Form::MIN => 'Please enter a value greater than or equal to %d.',
Form::MAX => 'Please enter a value less than or equal to %d.',
Form::RANGE => 'Please enter a value between %d and %d.',
Form::MAX_FILE_SIZE => 'The size of the uploaded file can be up to %d bytes.',
Form::MAX_POST_SIZE => 'The uploaded data exceeds the limit of %d bytes.',
Form::MIME_TYPE => 'The uploaded file is not in the expected format.',
Form::IMAGE => 'The uploaded file must be image in format JPEG, GIF or PNG.',
Controls\SelectBox::VALID => 'Please select a valid option.',
Controls\UploadControl::VALID => 'An error occurred during file upload.',
];
/** @internal */
public static function formatMessage(Rule $rule, $withValue = true)
{
$message = $rule->message;
if ($message instanceof Nette\Utils\IHtmlString) {
return $message;
} elseif ($message === null && is_string($rule->validator) && isset(static::$messages[$rule->validator])) {
$message = static::$messages[$rule->validator];
} elseif ($message == null) { // intentionally ==
trigger_error("Missing validation message for control '{$rule->control->getName()}'.", E_USER_WARNING);
}
if ($translator = $rule->control->getForm()->getTranslator()) {
$message = $translator->translate($message, is_int($rule->arg) ? $rule->arg : null);
}
$message = preg_replace_callback('#%(name|label|value|\d+\$[ds]|[ds])#', function ($m) use ($rule, $withValue) {
static $i = -1;
switch ($m[1]) {
case 'name': return $rule->control->getName();
case 'label': return $rule->control instanceof Controls\BaseControl
? rtrim($rule->control->translate($rule->control->caption), ':')
: null;
case 'value': return $withValue ? $rule->control->getValue() : $m[0];
default:
$args = is_array($rule->arg) ? $rule->arg : [$rule->arg];
$i = (int) $m[1] ? (int) $m[1] - 1 : $i + 1;
return isset($args[$i]) ? ($args[$i] instanceof IControl ? ($withValue ? $args[$i]->getValue() : "%$i") : $args[$i]) : '';
}
}, $message);
return $message;
}
/********************* default validators ****************d*g**/
/**
* Is control's value equal with second parameter?
* @return bool
*/
public static function validateEqual(IControl $control, $arg)
{
$value = $control->getValue();
foreach ((is_array($value) ? $value : [$value]) as $val) {
foreach ((is_array($arg) ? $arg : [$arg]) as $item) {
if ((string) $val === (string) $item) {
continue 2;
}
}
return false;
}
return true;
}
/**
* Is control's value not equal with second parameter?
* @return bool
*/
public static function validateNotEqual(IControl $control, $arg)
{
return !static::validateEqual($control, $arg);
}
/**
* Returns argument.
* @return bool
*/
public static function validateStatic(IControl $control, $arg)
{
return $arg;
}
/**
* Is control filled?
* @return bool
*/
public static function validateFilled(IControl $control)
{
return $control->isFilled();
}
/**
* Is control not filled?
* @return bool
*/
public static function validateBlank(IControl $control)
{
return !$control->isFilled();
}
/**
* Is control valid?
* @return bool
*/
public static function validateValid(Controls\BaseControl $control)
{
return $control->getRules()->validate();
}
/**
* Is a control's value number in specified range?
* @param IControl
* @param array
* @return bool
*/
public static function validateRange(IControl $control, $range)
{
$range = array_map(function ($v) {
return $v === '' ? null : $v;
}, $range);
return Validators::isInRange($control->getValue(), $range);
}
/**
* Is a control's value number greater than or equal to the specified minimum?
* @param IControl
* @param float
* @return bool
*/
public static function validateMin(IControl $control, $minimum)
{
return Validators::isInRange($control->getValue(), [$minimum === '' ? null : $minimum, null]);
}
/**
* Is a control's value number less than or equal to the specified maximum?
* @param IControl
* @param float
* @return bool
*/
public static function validateMax(IControl $control, $maximum)
{
return Validators::isInRange($control->getValue(), [null, $maximum === '' ? null : $maximum]);
}
/**
* Count/length validator. Range is array, min and max length pair.
* @param IControl
* @param array|int
* @return bool
*/
public static function validateLength(IControl $control, $range)
{
if (!is_array($range)) {
$range = [$range, $range];
}
$value = $control->getValue();
return Validators::isInRange(is_array($value) ? count($value) : Strings::length((string) $value), $range);
}
/**
* Has control's value minimal count/length?
* @param IControl
* @param int
* @return bool
*/
public static function validateMinLength(IControl $control, $length)
{
return static::validateLength($control, [$length, null]);
}
/**
* Is control's value count/length in limit?
* @param IControl
* @param int
* @return bool
*/
public static function validateMaxLength(IControl $control, $length)
{
return static::validateLength($control, [null, $length]);
}
/**
* Has been button pressed?
* @return bool
*/
public static function validateSubmitted(Controls\SubmitButton $control)
{
return $control->isSubmittedBy();
}
/**
* Is control's value valid email address?
* @return bool
*/
public static function validateEmail(IControl $control)
{
return Validators::isEmail($control->getValue());
}
/**
* Is control's value valid URL?
* @return bool
*/
public static function validateUrl(IControl $control)
{
if (Validators::isUrl($value = $control->getValue())) {
return true;
} elseif (Validators::isUrl($value = "http://$value")) {
$control->setValue($value);
return true;
}
return false;
}
/**
* Does the control's value match the regular expression?
* Case-sensitive to comply with the HTML5 <input /> pattern attribute behaviour
* @param string
* @return bool
*/
public static function validatePattern(IControl $control, $pattern, $caseInsensitive = false)
{
$value = $control->getValue() instanceof Nette\Http\FileUpload ? $control->getValue()->getName() : $control->getValue();
return (bool) Strings::match($value, "\x01^(?:$pattern)\\z\x01u" . ($caseInsensitive ? 'i' : ''));
}
public static function validatePatternCaseInsensitive(IControl $control, $pattern)
{
return self::validatePattern($control, $pattern, true);
}
/**
* Is a control's value decimal number?
* @return bool
*/
public static function validateInteger(IControl $control)
{
if (Validators::isNumericInt($value = $control->getValue())) {
if (!is_float($tmp = $value * 1)) { // bigint leave as string
$control->setValue($tmp);
}
return true;
}
return false;
}
/**
* Is a control's value float number?
* @return bool
*/
public static function validateFloat(IControl $control)
{
$value = str_replace([' ', ','], ['', '.'], $control->getValue());
if (Validators::isNumeric($value)) {
$control->setValue((float) $value);
return true;
}
return false;
}
/**
* Is file size in limit?
* @param int
* @return bool
*/
public static function validateFileSize(Controls\UploadControl $control, $limit)
{
foreach (static::toArray($control->getValue()) as $file) {
if ($file->getSize() > $limit || $file->getError() === UPLOAD_ERR_INI_SIZE) {
return false;
}
}
return true;
}
/**
* Has file specified mime type?
* @param IControl
* @param string|string[]
* @return bool
*/
public static function validateMimeType(Controls\UploadControl $control, $mimeType)
{
$mimeTypes = is_array($mimeType) ? $mimeType : explode(',', $mimeType);
foreach (static::toArray($control->getValue()) as $file) {
$type = strtolower($file->getContentType());
if (!in_array($type, $mimeTypes, true) && !in_array(preg_replace('#/.*#', '/*', $type), $mimeTypes, true)) {
return false;
}
}
return true;
}
/**
* Is file image?
* @return bool
*/
public static function validateImage(Controls\UploadControl $control)
{
foreach (static::toArray($control->getValue()) as $file) {
if (!$file->isImage()) {
return false;
}
}
return true;
}
/**
* @return array
*/
private static function toArray($value)
{
return $value instanceof Nette\Http\FileUpload ? [$value] : (array) $value;
}
}
+771
View File
@@ -0,0 +1,771 @@
/**
* NetteForms - simple form validation.
*
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
(function(global, factory) {
if (!global.JSON) {
return;
}
if (typeof define === 'function' && define.amd) {
define(function() {
return factory(global);
});
} else if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = factory(global);
} else {
var init = !global.Nette || !global.Nette.noInit;
global.Nette = factory(global);
if (init) {
global.Nette.initOnLoad();
}
}
}(typeof window !== 'undefined' ? window : this, function(window) {
'use strict';
var Nette = {};
Nette.formErrors = [];
Nette.version = '2.4';
/**
* Attaches a handler to an event for the element.
*/
Nette.addEvent = function(element, on, callback) {
if (on === 'DOMContentLoaded' && element.readyState !== 'loading') {
callback.call(this);
} else if (element.addEventListener) {
element.addEventListener(on, callback);
} else if (on === 'DOMContentLoaded') {
element.attachEvent('onreadystatechange', function() {
if (element.readyState === 'complete') {
callback.call(this);
}
});
} else {
element.attachEvent('on' + on, getHandler(callback));
}
};
function getHandler(callback) {
return function(e) {
return callback.call(this, e);
};
}
/**
* Returns the value of form element.
*/
Nette.getValue = function(elem) {
var i;
if (!elem) {
return null;
} else if (!elem.tagName) { // RadioNodeList, HTMLCollection, array
return elem[0] ? Nette.getValue(elem[0]) : null;
} else if (elem.type === 'radio') {
var elements = elem.form.elements; // prevents problem with name 'item' or 'namedItem'
for (i = 0; i < elements.length; i++) {
if (elements[i].name === elem.name && elements[i].checked) {
return elements[i].value;
}
}
return null;
} else if (elem.type === 'file') {
return elem.files || elem.value;
} else if (elem.tagName.toLowerCase() === 'select') {
var index = elem.selectedIndex,
options = elem.options,
values = [];
if (elem.type === 'select-one') {
return index < 0 ? null : options[index].value;
}
for (i = 0; i < options.length; i++) {
if (options[i].selected) {
values.push(options[i].value);
}
}
return values;
} else if (elem.name && elem.name.match(/\[\]$/)) { // multiple elements []
elements = elem.form.elements[elem.name].tagName ? [elem] : elem.form.elements[elem.name];
values = [];
for (i = 0; i < elements.length; i++) {
if (elements[i].type !== 'checkbox' || elements[i].checked) {
values.push(elements[i].value);
}
}
return values;
} else if (elem.type === 'checkbox') {
return elem.checked;
} else if (elem.tagName.toLowerCase() === 'textarea') {
return elem.value.replace('\r', '');
} else {
return elem.value.replace('\r', '').replace(/^\s+|\s+$/g, '');
}
};
/**
* Returns the effective value of form element.
*/
Nette.getEffectiveValue = function(elem) {
var val = Nette.getValue(elem);
if (elem.getAttribute) {
if (val === elem.getAttribute('data-nette-empty-value')) {
val = '';
}
}
return val;
};
/**
* Validates form element against given rules.
*/
Nette.validateControl = function(elem, rules, onlyCheck, value, emptyOptional) {
elem = elem.tagName ? elem : elem[0]; // RadioNodeList
rules = rules || Nette.parseJSON(elem.getAttribute('data-nette-rules'));
value = value === undefined ? {value: Nette.getEffectiveValue(elem)} : value;
for (var id = 0, len = rules.length; id < len; id++) {
var rule = rules[id],
op = rule.op.match(/(~)?([^?]+)/),
curElem = rule.control ? elem.form.elements.namedItem(rule.control) : elem;
rule.neg = op[1];
rule.op = op[2];
rule.condition = !!rule.rules;
if (!curElem) {
continue;
} else if (rule.op === 'optional') {
emptyOptional = !Nette.validateRule(elem, ':filled', null, value);
continue;
} else if (emptyOptional && !rule.condition && rule.op !== ':filled') {
continue;
}
curElem = curElem.tagName ? curElem : curElem[0]; // RadioNodeList
var curValue = elem === curElem ? value : {value: Nette.getEffectiveValue(curElem)},
success = Nette.validateRule(curElem, rule.op, rule.arg, curValue);
if (success === null) {
continue;
} else if (rule.neg) {
success = !success;
}
if (rule.condition && success) {
if (!Nette.validateControl(elem, rule.rules, onlyCheck, value, rule.op === ':blank' ? false : emptyOptional)) {
return false;
}
} else if (!rule.condition && !success) {
if (Nette.isDisabled(curElem)) {
continue;
}
if (!onlyCheck) {
var arr = Nette.isArray(rule.arg) ? rule.arg : [rule.arg],
message = rule.msg.replace(/%(value|\d+)/g, function(foo, m) {
return Nette.getValue(m === 'value' ? curElem : elem.form.elements.namedItem(arr[m].control));
});
Nette.addError(curElem, message);
}
return false;
}
}
if (elem.type === 'number' && !elem.validity.valid) {
if (!onlyCheck) {
Nette.addError(elem, 'Please enter a valid value.');
}
return false;
}
return true;
};
/**
* Validates whole form.
*/
Nette.validateForm = function(sender, onlyCheck) {
var form = sender.form || sender,
scope = false;
Nette.formErrors = [];
if (form['nette-submittedBy'] && form['nette-submittedBy'].getAttribute('formnovalidate') !== null) {
var scopeArr = Nette.parseJSON(form['nette-submittedBy'].getAttribute('data-nette-validation-scope'));
if (scopeArr.length) {
scope = new RegExp('^(' + scopeArr.join('-|') + '-)');
} else {
Nette.showFormErrors(form, []);
return true;
}
}
var radios = {}, i, elem;
for (i = 0; i < form.elements.length; i++) {
elem = form.elements[i];
if (elem.tagName && !(elem.tagName.toLowerCase() in {input: 1, select: 1, textarea: 1, button: 1})) {
continue;
} else if (elem.type === 'radio') {
if (radios[elem.name]) {
continue;
}
radios[elem.name] = true;
}
if ((scope && !elem.name.replace(/]\[|\[|]|$/g, '-').match(scope)) || Nette.isDisabled(elem)) {
continue;
}
if (!Nette.validateControl(elem, null, onlyCheck) && !Nette.formErrors.length) {
return false;
}
}
var success = !Nette.formErrors.length;
Nette.showFormErrors(form, Nette.formErrors);
return success;
};
/**
* Check if input is disabled.
*/
Nette.isDisabled = function(elem) {
if (elem.type === 'radio') {
for (var i = 0, elements = elem.form.elements; i < elements.length; i++) {
if (elements[i].name === elem.name && !elements[i].disabled) {
return false;
}
}
return true;
}
return elem.disabled;
};
/**
* Adds error message to the queue.
*/
Nette.addError = function(elem, message) {
Nette.formErrors.push({
element: elem,
message: message
});
};
/**
* Display error messages.
*/
Nette.showFormErrors = function(form, errors) {
var messages = [],
focusElem;
for (var i = 0; i < errors.length; i++) {
var elem = errors[i].element,
message = errors[i].message;
if (!Nette.inArray(messages, message)) {
messages.push(message);
if (!focusElem && elem.focus) {
focusElem = elem;
}
}
}
if (messages.length) {
alert(messages.join('\n'));
if (focusElem) {
focusElem.focus();
}
}
};
/**
* Expand rule argument.
*/
Nette.expandRuleArgument = function(form, arg) {
if (arg && arg.control) {
var control = form.elements.namedItem(arg.control),
value = {value: Nette.getEffectiveValue(control)};
Nette.validateControl(control, null, true, value);
arg = value.value;
}
return arg;
};
var preventFiltering = false;
/**
* Validates single rule.
*/
Nette.validateRule = function(elem, op, arg, value) {
value = value === undefined ? {value: Nette.getEffectiveValue(elem)} : value;
if (op.charAt(0) === ':') {
op = op.substr(1);
}
op = op.replace('::', '_');
op = op.replace(/\\/g, '');
var arr = Nette.isArray(arg) ? arg.slice(0) : [arg];
if (!preventFiltering) {
preventFiltering = true;
for (var i = 0, len = arr.length; i < len; i++) {
arr[i] = Nette.expandRuleArgument(elem.form, arr[i]);
}
preventFiltering = false;
}
return Nette.validators[op]
? Nette.validators[op](elem, Nette.isArray(arg) ? arr : arr[0], value.value, value)
: null;
};
Nette.validators = {
filled: function(elem, arg, val) {
if (elem.type === 'number' && elem.validity.badInput) {
return true;
}
return val !== '' && val !== false && val !== null
&& (!Nette.isArray(val) || !!val.length)
&& (!window.FileList || !(val instanceof window.FileList) || val.length);
},
blank: function(elem, arg, val) {
return !Nette.validators.filled(elem, arg, val);
},
valid: function(elem) {
return Nette.validateControl(elem, null, true);
},
equal: function(elem, arg, val) {
if (arg === undefined) {
return null;
}
function toString(val) {
if (typeof val === 'number' || typeof val === 'string') {
return '' + val;
} else {
return val === true ? '1' : '';
}
}
val = Nette.isArray(val) ? val : [val];
arg = Nette.isArray(arg) ? arg : [arg];
loop:
for (var i1 = 0, len1 = val.length; i1 < len1; i1++) {
for (var i2 = 0, len2 = arg.length; i2 < len2; i2++) {
if (toString(val[i1]) === toString(arg[i2])) {
continue loop;
}
}
return false;
}
return true;
},
notEqual: function(elem, arg, val) {
return arg === undefined ? null : !Nette.validators.equal(elem, arg, val);
},
minLength: function(elem, arg, val) {
if (elem.type === 'number') {
if (elem.validity.tooShort) {
return false;
} else if (elem.validity.badInput) {
return null;
}
}
return val.length >= arg;
},
maxLength: function(elem, arg, val) {
if (elem.type === 'number') {
if (elem.validity.tooLong) {
return false;
} else if (elem.validity.badInput) {
return null;
}
}
return val.length <= arg;
},
length: function(elem, arg, val) {
if (elem.type === 'number') {
if (elem.validity.tooShort || elem.validity.tooLong) {
return false;
} else if (elem.validity.badInput) {
return null;
}
}
arg = Nette.isArray(arg) ? arg : [arg, arg];
return (arg[0] === null || val.length >= arg[0]) && (arg[1] === null || val.length <= arg[1]);
},
email: function(elem, arg, val) {
return (/^("([ !#-[\]-~]|\\[ -~])+"|[-a-z0-9!#$%&'*+/=?^_`{|}~]+(\.[-a-z0-9!#$%&'*+/=?^_`{|}~]+)*)@([0-9a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,61}[0-9a-z\u00C0-\u02FF\u0370-\u1EFF])?\.)+[a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,17}[a-z\u00C0-\u02FF\u0370-\u1EFF])?$/i).test(val);
},
url: function(elem, arg, val, value) {
if (!(/^[a-z\d+.-]+:/).test(val)) {
val = 'http://' + val;
}
if ((/^https?:\/\/((([-_0-9a-z\u00C0-\u02FF\u0370-\u1EFF]+\.)*[0-9a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,61}[0-9a-z\u00C0-\u02FF\u0370-\u1EFF])?\.)?[a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,17}[a-z\u00C0-\u02FF\u0370-\u1EFF])?|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[[0-9a-f:]{3,39}\])(:\d{1,5})?(\/\S*)?$/i).test(val)) {
value.value = val;
return true;
}
return false;
},
regexp: function(elem, arg, val) {
var parts = typeof arg === 'string' ? arg.match(/^\/(.*)\/([imu]*)$/) : false;
try {
return parts && (new RegExp(parts[1], parts[2].replace('u', ''))).test(val);
} catch (e) {} // eslint-disable-line no-empty
},
pattern: function(elem, arg, val, value, caseInsensitive) {
if (typeof arg !== 'string') {
return null;
}
try {
try {
var regExp = new RegExp('^(?:' + arg + ')$', caseInsensitive ? 'ui' : 'u');
} catch (e) {
regExp = new RegExp('^(?:' + arg + ')$', caseInsensitive ? 'i' : '');
}
if (window.FileList && val instanceof FileList) {
for (var i = 0; i < val.length; i++) {
if (!regExp.test(val[i].name)) {
return false;
}
}
return true;
}
return regExp.test(val);
} catch (e) {} // eslint-disable-line no-empty
},
patternCaseInsensitive: function(elem, arg, val) {
return Nette.validators.pattern(elem, arg, val, null, true);
},
integer: function(elem, arg, val) {
if (elem.type === 'number' && elem.validity.badInput) {
return false;
}
return (/^-?[0-9]+$/).test(val);
},
'float': function(elem, arg, val, value) {
if (elem.type === 'number' && elem.validity.badInput) {
return false;
}
val = val.replace(/ +/g, '').replace(/,/g, '.');
if ((/^-?[0-9]*\.?[0-9]+$/).test(val)) {
value.value = val;
return true;
}
return false;
},
min: function(elem, arg, val) {
if (elem.type === 'number') {
if (elem.validity.rangeUnderflow) {
return false;
} else if (elem.validity.badInput) {
return null;
}
}
return arg === null || parseFloat(val) >= arg;
},
max: function(elem, arg, val) {
if (elem.type === 'number') {
if (elem.validity.rangeOverflow) {
return false;
} else if (elem.validity.badInput) {
return null;
}
}
return arg === null || parseFloat(val) <= arg;
},
range: function(elem, arg, val) {
if (elem.type === 'number') {
if (elem.validity.rangeUnderflow || elem.validity.rangeOverflow) {
return false;
} else if (elem.validity.badInput) {
return null;
}
}
return Nette.isArray(arg) ?
((arg[0] === null || parseFloat(val) >= arg[0]) && (arg[1] === null || parseFloat(val) <= arg[1])) : null;
},
submitted: function(elem) {
return elem.form['nette-submittedBy'] === elem;
},
fileSize: function(elem, arg, val) {
if (window.FileList) {
for (var i = 0; i < val.length; i++) {
if (val[i].size > arg) {
return false;
}
}
}
return true;
},
image: function (elem, arg, val) {
if (window.FileList && val instanceof window.FileList) {
for (var i = 0; i < val.length; i++) {
var type = val[i].type;
if (type && type !== 'image/gif' && type !== 'image/png' && type !== 'image/jpeg') {
return false;
}
}
}
return true;
},
'static': function (elem, arg) {
return arg;
}
};
/**
* Process all toggles in form.
*/
Nette.toggleForm = function(form, elem) {
var i;
Nette.toggles = {};
for (i = 0; i < form.elements.length; i++) {
if (form.elements[i].tagName.toLowerCase() in {input: 1, select: 1, textarea: 1, button: 1}) {
Nette.toggleControl(form.elements[i], null, null, !elem);
}
}
for (i in Nette.toggles) {
Nette.toggle(i, Nette.toggles[i], elem);
}
};
/**
* Process toggles on form element.
*/
Nette.toggleControl = function(elem, rules, success, firsttime, value) {
rules = rules || Nette.parseJSON(elem.getAttribute('data-nette-rules'));
value = value === undefined ? {value: Nette.getEffectiveValue(elem)} : value;
var has = false,
handled = [],
handler = function () {
Nette.toggleForm(elem.form, elem);
},
curSuccess;
for (var id = 0, len = rules.length; id < len; id++) {
var rule = rules[id],
op = rule.op.match(/(~)?([^?]+)/),
curElem = rule.control ? elem.form.elements.namedItem(rule.control) : elem;
if (!curElem) {
continue;
}
curSuccess = success;
if (success !== false) {
rule.neg = op[1];
rule.op = op[2];
var curValue = elem === curElem ? value : {value: Nette.getEffectiveValue(curElem)};
curSuccess = Nette.validateRule(curElem, rule.op, rule.arg, curValue);
if (curSuccess === null) {
continue;
} else if (rule.neg) {
curSuccess = !curSuccess;
}
if (!rule.rules) {
success = curSuccess;
}
}
if ((rule.rules && Nette.toggleControl(elem, rule.rules, curSuccess, firsttime, value)) || rule.toggle) {
has = true;
if (firsttime) {
var oldIE = !document.addEventListener, // IE < 9
name = curElem.tagName ? curElem.name : curElem[0].name,
els = curElem.tagName ? curElem.form.elements : curElem;
for (var i = 0; i < els.length; i++) {
if (els[i].name === name && !Nette.inArray(handled, els[i])) {
Nette.addEvent(els[i], oldIE && els[i].type in {checkbox: 1, radio: 1} ? 'click' : 'change', handler);
handled.push(els[i]);
}
}
}
for (var id2 in rule.toggle || []) {
if (Object.prototype.hasOwnProperty.call(rule.toggle, id2)) {
Nette.toggles[id2] = Nette.toggles[id2] || (rule.toggle[id2] ? curSuccess : !curSuccess);
}
}
}
}
return has;
};
Nette.parseJSON = function(s) {
return (s || '').substr(0, 3) === '{op'
? eval('[' + s + ']') // backward compatibility with Nette 2.0.x
: JSON.parse(s || '[]');
};
/**
* Displays or hides HTML element.
*/
Nette.toggle = function(id, visible, srcElement) { // eslint-disable-line no-unused-vars
var elem = document.getElementById(id);
if (elem) {
elem.style.display = visible ? '' : 'none';
}
};
/**
* Setup handlers.
*/
Nette.initForm = function(form) {
Nette.toggleForm(form);
if (form.noValidate) {
return;
}
form.noValidate = true;
Nette.addEvent(form, 'submit', function(e) {
if (!Nette.validateForm(form)) {
if (e && e.stopPropagation) {
e.stopPropagation();
e.preventDefault();
} else if (window.event) {
event.cancelBubble = true;
event.returnValue = false;
}
}
});
};
/**
* @private
*/
Nette.initOnLoad = function() {
Nette.addEvent(document, 'DOMContentLoaded', function() {
for (var i = 0; i < document.forms.length; i++) {
var form = document.forms[i];
for (var j = 0; j < form.elements.length; j++) {
if (form.elements[j].getAttribute('data-nette-rules')) {
Nette.initForm(form);
break;
}
}
}
Nette.addEvent(document.body, 'click', function(e) {
var target = e.target || e.srcElement;
while (target) {
if (target.form && target.type in {submit: 1, image: 1}) {
target.form['nette-submittedBy'] = target;
break;
}
target = target.parentNode;
}
});
});
};
/**
* Determines whether the argument is an array.
*/
Nette.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
/**
* Search for a specified value within an array.
*/
Nette.inArray = function(arr, val) {
if ([].indexOf) {
return arr.indexOf(val) > -1;
} else {
for (var i = 0; i < arr.length; i++) {
if (arr[i] === val) {
return true;
}
}
return false;
}
};
/**
* Converts string to web safe characters [a-z0-9-] text.
*/
Nette.webalize = function(s) {
s = s.toLowerCase();
var res = '', i, ch;
for (i = 0; i < s.length; i++) {
ch = Nette.webalizeTable[s.charAt(i)];
res += ch ? ch : s.charAt(i);
}
return res.replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
};
Nette.webalizeTable = {\u00e1: 'a', \u00e4: 'a', \u010d: 'c', \u010f: 'd', \u00e9: 'e', \u011b: 'e', \u00ed: 'i', \u013e: 'l', \u0148: 'n', \u00f3: 'o', \u00f4: 'o', \u0159: 'r', \u0161: 's', \u0165: 't', \u00fa: 'u', \u016f: 'u', \u00fd: 'y', \u017e: 'z'};
return Nette;
}));
+22
View File
@@ -0,0 +1,22 @@
/*! netteForms.js | (c) 2004 David Grudl (https://davidgrudl.com) */
(function(e,p){if(e.JSON)if("function"===typeof define&&define.amd)define(function(){return p(e)});else if("object"===typeof module&&"object"===typeof module.exports)module.exports=p(e);else{var d=!e.Nette||!e.Nette.noInit;e.Nette=p(e);d&&e.Nette.initOnLoad()}})("undefined"!==typeof window?window:this,function(e){function p(a){return function(b){return a.call(this,b)}}var d={formErrors:[],version:"2.4",addEvent:function(a,b,c){"DOMContentLoaded"===b&&"loading"!==a.readyState?c.call(this):a.addEventListener?
a.addEventListener(b,c):"DOMContentLoaded"===b?a.attachEvent("onreadystatechange",function(){"complete"===a.readyState&&c.call(this)}):a.attachEvent("on"+b,p(c))},getValue:function(a){var b;if(a){if(a.tagName){if("radio"===a.type){var c=a.form.elements;for(b=0;b<c.length;b++)if(c[b].name===a.name&&c[b].checked)return c[b].value;return null}if("file"===a.type)return a.files||a.value;if("select"===a.tagName.toLowerCase()){b=a.selectedIndex;c=a.options;var f=[];if("select-one"===a.type)return 0>b?null:
c[b].value;for(b=0;b<c.length;b++)c[b].selected&&f.push(c[b].value);return f}if(a.name&&a.name.match(/\[\]$/)){c=a.form.elements[a.name].tagName?[a]:a.form.elements[a.name];f=[];for(b=0;b<c.length;b++)("checkbox"!==c[b].type||c[b].checked)&&f.push(c[b].value);return f}return"checkbox"===a.type?a.checked:"textarea"===a.tagName.toLowerCase()?a.value.replace("\r",""):a.value.replace("\r","").replace(/^\s+|\s+$/g,"")}return a[0]?d.getValue(a[0]):null}return null},getEffectiveValue:function(a){var b=d.getValue(a);
a.getAttribute&&b===a.getAttribute("data-nette-empty-value")&&(b="");return b},validateControl:function(a,b,c,f,q){a=a.tagName?a:a[0];b=b||d.parseJSON(a.getAttribute("data-nette-rules"));f=void 0===f?{value:d.getEffectiveValue(a)}:f;for(var g=0,k=b.length;g<k;g++){var h=b[g],l=h.op.match(/(~)?([^?]+)/),e=h.control?a.form.elements.namedItem(h.control):a;h.neg=l[1];h.op=l[2];h.condition=!!h.rules;if(e)if("optional"===h.op)q=!d.validateRule(a,":filled",null,f);else if(!q||h.condition||":filled"===h.op)if(e=
e.tagName?e:e[0],l=a===e?f:{value:d.getEffectiveValue(e)},l=d.validateRule(e,h.op,h.arg,l),null!==l)if(h.neg&&(l=!l),h.condition&&l){if(!d.validateControl(a,h.rules,c,f,":blank"===h.op?!1:q))return!1}else if(!h.condition&&!l&&!d.isDisabled(e)){if(!c){var p=d.isArray(h.arg)?h.arg:[h.arg];b=h.msg.replace(/%(value|\d+)/g,function(b,c){return d.getValue("value"===c?e:a.form.elements.namedItem(p[c].control))});d.addError(e,b)}return!1}}return"number"!==a.type||a.validity.valid?!0:(c||d.addError(a,"Please enter a valid value."),
!1)},validateForm:function(a,b){var c=a.form||a,f=!1;d.formErrors=[];if(c["nette-submittedBy"]&&null!==c["nette-submittedBy"].getAttribute("formnovalidate"))if(f=d.parseJSON(c["nette-submittedBy"].getAttribute("data-nette-validation-scope")),f.length)f=new RegExp("^("+f.join("-|")+"-)");else return d.showFormErrors(c,[]),!0;var e={},g;for(g=0;g<c.elements.length;g++){var k=c.elements[g];if(!k.tagName||k.tagName.toLowerCase()in{input:1,select:1,textarea:1,button:1}){if("radio"===k.type){if(e[k.name])continue;
e[k.name]=!0}if(!(f&&!k.name.replace(/]\[|\[|]|$/g,"-").match(f)||d.isDisabled(k)||d.validateControl(k,null,b)||d.formErrors.length))return!1}}f=!d.formErrors.length;d.showFormErrors(c,d.formErrors);return f},isDisabled:function(a){if("radio"===a.type){for(var b=0,c=a.form.elements;b<c.length;b++)if(c[b].name===a.name&&!c[b].disabled)return!1;return!0}return a.disabled},addError:function(a,b){d.formErrors.push({element:a,message:b})},showFormErrors:function(a,b){for(var c=[],f,e=0;e<b.length;e++){var g=
b[e].element,k=b[e].message;d.inArray(c,k)||(c.push(k),!f&&g.focus&&(f=g))}c.length&&(alert(c.join("\n")),f&&f.focus())},expandRuleArgument:function(a,b){if(b&&b.control){var c=a.elements.namedItem(b.control),f={value:d.getEffectiveValue(c)};d.validateControl(c,null,!0,f);b=f.value}return b}},t=!1;d.validateRule=function(a,b,c,f){f=void 0===f?{value:d.getEffectiveValue(a)}:f;":"===b.charAt(0)&&(b=b.substr(1));b=b.replace("::","_");b=b.replace(/\\/g,"");var e=d.isArray(c)?c.slice(0):[c];if(!t){t=!0;
for(var g=0,k=e.length;g<k;g++)e[g]=d.expandRuleArgument(a.form,e[g]);t=!1}return d.validators[b]?d.validators[b](a,d.isArray(c)?e:e[0],f.value,f):null};d.validators={filled:function(a,b,c){return"number"===a.type&&a.validity.badInput?!0:""!==c&&!1!==c&&null!==c&&(!d.isArray(c)||!!c.length)&&(!e.FileList||!(c instanceof e.FileList)||c.length)},blank:function(a,b,c){return!d.validators.filled(a,b,c)},valid:function(a){return d.validateControl(a,null,!0)},equal:function(a,b,c){function f(a){return"number"===
typeof a||"string"===typeof a?""+a:!0===a?"1":""}if(void 0===b)return null;c=d.isArray(c)?c:[c];b=d.isArray(b)?b:[b];a=0;var e=c.length;a:for(;a<e;a++){for(var g=0,k=b.length;g<k;g++)if(f(c[a])===f(b[g]))continue a;return!1}return!0},notEqual:function(a,b,c){return void 0===b?null:!d.validators.equal(a,b,c)},minLength:function(a,b,c){if("number"===a.type){if(a.validity.tooShort)return!1;if(a.validity.badInput)return null}return c.length>=b},maxLength:function(a,b,c){if("number"===a.type){if(a.validity.tooLong)return!1;
if(a.validity.badInput)return null}return c.length<=b},length:function(a,b,c){if("number"===a.type){if(a.validity.tooShort||a.validity.tooLong)return!1;if(a.validity.badInput)return null}b=d.isArray(b)?b:[b,b];return(null===b[0]||c.length>=b[0])&&(null===b[1]||c.length<=b[1])},email:function(a,b,c){return/^("([ !#-[\]-~]|\\[ -~])+"|[-a-z0-9!#$%&'*+/=?^_`{|}~]+(\.[-a-z0-9!#$%&'*+/=?^_`{|}~]+)*)@([0-9a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,61}[0-9a-z\u00C0-\u02FF\u0370-\u1EFF])?\.)+[a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,17}[a-z\u00C0-\u02FF\u0370-\u1EFF])?$/i.test(c)},
url:function(a,b,c,d){/^[a-z\d+.-]+:/.test(c)||(c="http://"+c);return/^https?:\/\/((([-_0-9a-z\u00C0-\u02FF\u0370-\u1EFF]+\.)*[0-9a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,61}[0-9a-z\u00C0-\u02FF\u0370-\u1EFF])?\.)?[a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,17}[a-z\u00C0-\u02FF\u0370-\u1EFF])?|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[[0-9a-f:]{3,39}\])(:\d{1,5})?(\/\S*)?$/i.test(c)?(d.value=c,!0):!1},regexp:function(a,b,c){a="string"===typeof b?b.match(/^\/(.*)\/([imu]*)$/):
!1;try{return a&&(new RegExp(a[1],a[2].replace("u",""))).test(c)}catch(f){}},pattern:function(a,b,c,d,q){if("string"!==typeof b)return null;try{try{var f=new RegExp("^(?:"+b+")$",q?"ui":"u")}catch(k){f=new RegExp("^(?:"+b+")$",q?"i":"")}if(e.FileList&&c instanceof FileList){for(a=0;a<c.length;a++)if(!f.test(c[a].name))return!1;return!0}return f.test(c)}catch(k){}},patternCaseInsensitive:function(a,b,c){return d.validators.pattern(a,b,c,null,!0)},integer:function(a,b,c){return"number"===a.type&&a.validity.badInput?
!1:/^-?[0-9]+$/.test(c)},"float":function(a,b,c,d){if("number"===a.type&&a.validity.badInput)return!1;c=c.replace(/ +/g,"").replace(/,/g,".");return/^-?[0-9]*\.?[0-9]+$/.test(c)?(d.value=c,!0):!1},min:function(a,b,c){if("number"===a.type){if(a.validity.rangeUnderflow)return!1;if(a.validity.badInput)return null}return null===b||parseFloat(c)>=b},max:function(a,b,c){if("number"===a.type){if(a.validity.rangeOverflow)return!1;if(a.validity.badInput)return null}return null===b||parseFloat(c)<=b},range:function(a,
b,c){if("number"===a.type){if(a.validity.rangeUnderflow||a.validity.rangeOverflow)return!1;if(a.validity.badInput)return null}return d.isArray(b)?(null===b[0]||parseFloat(c)>=b[0])&&(null===b[1]||parseFloat(c)<=b[1]):null},submitted:function(a){return a.form["nette-submittedBy"]===a},fileSize:function(a,b,c){if(e.FileList)for(a=0;a<c.length;a++)if(c[a].size>b)return!1;return!0},image:function(a,b,c){if(e.FileList&&c instanceof e.FileList)for(a=0;a<c.length;a++)if((b=c[a].type)&&"image/gif"!==b&&"image/png"!==
b&&"image/jpeg"!==b)return!1;return!0},"static":function(a,b){return b}};d.toggleForm=function(a,b){var c;d.toggles={};for(c=0;c<a.elements.length;c++)a.elements[c].tagName.toLowerCase()in{input:1,select:1,textarea:1,button:1}&&d.toggleControl(a.elements[c],null,null,!b);for(c in d.toggles)d.toggle(c,d.toggles[c],b)};d.toggleControl=function(a,b,c,f,e){b=b||d.parseJSON(a.getAttribute("data-nette-rules"));e=void 0===e?{value:d.getEffectiveValue(a)}:e;for(var g=!1,k=[],h=function(){d.toggleForm(a.form,
a)},l,p=0,q=b.length;p<q;p++){var n=b[p],u=n.op.match(/(~)?([^?]+)/),m=n.control?a.form.elements.namedItem(n.control):a;if(m){l=c;if(!1!==c){n.neg=u[1];n.op=u[2];l=a===m?e:{value:d.getEffectiveValue(m)};l=d.validateRule(m,n.op,n.arg,l);if(null===l)continue;else n.neg&&(l=!l);n.rules||(c=l)}if(n.rules&&d.toggleControl(a,n.rules,l,f,e)||n.toggle){g=!0;if(f){u=!document.addEventListener;var t=m.tagName?m.name:m[0].name;m=m.tagName?m.form.elements:m;for(var r=0;r<m.length;r++)m[r].name!==t||d.inArray(k,
m[r])||(d.addEvent(m[r],u&&m[r].type in{checkbox:1,radio:1}?"click":"change",h),k.push(m[r]))}for(var v in n.toggle||[])Object.prototype.hasOwnProperty.call(n.toggle,v)&&(d.toggles[v]=d.toggles[v]||(n.toggle[v]?l:!l))}}}return g};d.parseJSON=function(a){return"{op"===(a||"").substr(0,3)?eval("["+a+"]"):JSON.parse(a||"[]")};d.toggle=function(a,b,c){if(a=document.getElementById(a))a.style.display=b?"":"none"};d.initForm=function(a){d.toggleForm(a);a.noValidate||(a.noValidate=!0,d.addEvent(a,"submit",
function(b){d.validateForm(a)||(b&&b.stopPropagation?(b.stopPropagation(),b.preventDefault()):e.event&&(event.cancelBubble=!0,event.returnValue=!1))}))};d.initOnLoad=function(){d.addEvent(document,"DOMContentLoaded",function(){for(var a=0;a<document.forms.length;a++)for(var b=document.forms[a],c=0;c<b.elements.length;c++)if(b.elements[c].getAttribute("data-nette-rules")){d.initForm(b);break}d.addEvent(document.body,"click",function(a){for(a=a.target||a.srcElement;a;){if(a.form&&a.type in{submit:1,
image:1}){a.form["nette-submittedBy"]=a;break}a=a.parentNode}})})};d.isArray=function(a){return"[object Array]"===Object.prototype.toString.call(a)};d.inArray=function(a,b){if([].indexOf)return-1<a.indexOf(b);for(var c=0;c<a.length;c++)if(a[c]===b)return!0;return!1};d.webalize=function(a){a=a.toLowerCase();var b="",c;for(c=0;c<a.length;c++){var e=d.webalizeTable[a.charAt(c)];b+=e?e:a.charAt(c)}return b.replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")};d.webalizeTable={"\u00e1":"a","\u00e4":"a","\u010d":"c",
"\u010f":"d","\u00e9":"e","\u011b":"e","\u00ed":"i","\u013e":"l","\u0148":"n","\u00f3":"o","\u00f4":"o","\u0159":"r","\u0161":"s","\u0165":"t","\u00fa":"u","\u016f":"u","\u00fd":"y","\u017e":"z"};return d});