diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md deleted file mode 100644 index a4cd12634..000000000 --- a/.github/ISSUE_TEMPLATE/Bug_report.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: "🐛 Bug Report" -about: "If something isn't working as expected 🤔" - ---- - -Version: ?.?.? - -### Bug Description -... A clear and concise description of what the bug is. A good bug report shouldn't leave others needing to chase you up for more information. - -### Steps To Reproduce -... If possible a minimal demo of the problem ... - -### Expected Behavior -... A clear and concise description of what you expected to happen. - -### Possible Solution -... Only if you have suggestions on a fix for the bug diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md deleted file mode 100644 index d2e219489..000000000 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: "🚀 Feature Request" -about: "I have a suggestion (and may want to implement it) 🙂" - ---- - -- Is your feature request related to a problem? Please describe. -- Explain your intentions. -- It's up to you to make a strong case to convince the project's developers of the merits of this feature. diff --git a/.github/ISSUE_TEMPLATE/Support_question.md b/.github/ISSUE_TEMPLATE/Support_question.md deleted file mode 100644 index 75c48b6ed..000000000 --- a/.github/ISSUE_TEMPLATE/Support_question.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -name: "🤗 Support Question" -about: "If you have a question 💬, please check out our forum!" - ---- - ---------------^ Click "Preview" for a nicer view! -We primarily use GitHub as an issue tracker; for usage and support questions, please check out these resources below. Thanks! 😁. - -* Nette Forum: https://forum.nette.org -* Nette Gitter: https://gitter.im/nette/nette -* Slack (czech): https://pehapkari.slack.com/messages/C2R30BLKA diff --git a/.github/ISSUE_TEMPLATE/Support_us.md b/.github/ISSUE_TEMPLATE/Support_us.md deleted file mode 100644 index 92d8a4c3a..000000000 --- a/.github/ISSUE_TEMPLATE/Support_us.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: "❤️ Support us" -about: "If you would like to support our efforts in maintaining this project 🙌" - ---- - ---------------^ Click "Preview" for a nicer view! - -> https://nette.org/donate - -Help support Nette! - -We develop Nette Framework for more than 14 years. In order to make your life more comfortable. Nette cares about the safety of your sites. Nette saves you time. And gives job opportunities. - -Nette earns you money. And is absolutely free. - -To ensure future development and improving the documentation, we need your donation. - -Whether you are chief of IT company which benefits from Nette, or developer who goes for advice on our forum, if you like Nette, [please make a donation now](https://nette.org/donate). - -Thank you! diff --git a/.github/funding.yml b/.github/funding.yml deleted file mode 100644 index 25adc9520..000000000 --- a/.github/funding.yml +++ /dev/null @@ -1,2 +0,0 @@ -github: dg -custom: "https://nette.org/donate" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index f8aa3f408..000000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,11 +0,0 @@ -- bug fix / new feature? -- BC break? yes/no -- doc PR: nette/docs#??? - - diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index bd07bb660..b2068accb 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 with: - php-version: 7.2 + php-version: 8.0 coverage: none - run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 coverage: none - run: composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 28fd47e63..e09916b5e 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 coverage: none - run: composer install --no-progress --prefer-dist diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 266902db6..82b6f1861 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: ['7.2', '7.3', '7.4', '8.0', '8.1'] + php: ['8.0', '8.1'] fail-fast: false @@ -35,7 +35,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 with: - php-version: 7.2 + php-version: 8.0 coverage: none - run: composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable @@ -49,7 +49,7 @@ jobs: - uses: actions/checkout@v2 - uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.0 coverage: none - run: composer install --no-progress --prefer-dist diff --git a/composer.json b/composer.json index 9ae31e55f..00d6a31b8 100644 --- a/composer.json +++ b/composer.json @@ -15,22 +15,22 @@ } ], "require": { - "php": ">=7.2 <8.2", - "nette/component-model": "^3.0", - "nette/http": "^3.1", - "nette/utils": "^3.2.1" + "php": ">=8.0 <8.2", + "nette/component-model": "^4.0", + "nette/http": "^4.0", + "nette/utils": "^4.0" }, "require-dev": { - "nette/application": "^3.0", - "nette/di": "^3.0", - "nette/tester": "^2.0", - "latte/latte": "^2.10.2", - "tracy/tracy": "^2.4", - "phpstan/phpstan-nette": "^0.12" + "nette/application": "^4.0", + "nette/di": "^4.0", + "nette/tester": "^2.4", + "latte/latte": "^2.10.2 || ^3.0", + "tracy/tracy": "^2.8", + "phpstan/phpstan-nette": "^1.0" }, "conflict": { "nette/di": "<3.0-stable", - "latte/latte": ">=3.0" + "latte/latte": ">=3.1" }, "autoload": { "classmap": ["src/"] @@ -42,7 +42,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "4.0-dev" } } } diff --git a/contributing.md b/contributing.md deleted file mode 100644 index 184152c02..000000000 --- a/contributing.md +++ /dev/null @@ -1,33 +0,0 @@ -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: diff --git a/examples/custom-control.php b/examples/custom-control.php index 0d21740ed..b8e06b32b 100644 --- a/examples/custom-control.php +++ b/examples/custom-control.php @@ -18,12 +18,11 @@ class DateInput extends Nette\Forms\Controls\BaseControl { - /** @var string */ - private $day = ''; + private string $day = ''; - private $month = ''; + private string $month = ''; - private $year = ''; + private string $year = ''; public function __construct($label = null) @@ -88,7 +87,7 @@ public function getControl() . Helpers::createSelectBox( [1 => 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], [], - $this->month + $this->month, )->name($name . '[month]') . Html::el('input', [ diff --git a/examples/latte.php b/examples/latte.php index 32bc421d4..3995b8441 100644 --- a/examples/latte.php +++ b/examples/latte.php @@ -39,8 +39,6 @@ } $latte = new Latte\Engine; -$latte->onCompile[] = function ($latte) { - Nette\Bridges\FormsLatte\FormMacros::install($latte->getCompiler()); -}; +$latte->addExtension(new Nette\Bridges\FormsLatte\FormsExtension); $latte->render(__DIR__ . '/latte/page.latte', ['form' => $form]); diff --git a/readme.md b/readme.md index 945ea9a94..f6196c3a2 100644 --- a/readme.md +++ b/readme.md @@ -44,7 +44,7 @@ The recommended way to install is via Composer: composer require nette/forms ``` -It requires PHP version 7.2 and supports PHP up to 8.1. +It requires PHP version 8.0 and supports PHP up to 8.1. Client-side support can be installed with npm or yarn: diff --git a/src/Bridges/FormsDI/FormsExtension.php b/src/Bridges/FormsDI/FormsExtension.php index 58281781b..5b4989f89 100644 --- a/src/Bridges/FormsDI/FormsExtension.php +++ b/src/Bridges/FormsDI/FormsExtension.php @@ -21,7 +21,7 @@ public function __construct() { $this->config = new class { /** @var string[] */ - public $messages = []; + public array $messages = []; }; } diff --git a/src/Bridges/FormsLatte/FormMacros.php b/src/Bridges/FormsLatte/FormMacros.php index 3b125a8ff..c92c1c59b 100644 --- a/src/Bridges/FormsLatte/FormMacros.php +++ b/src/Bridges/FormsLatte/FormMacros.php @@ -17,7 +17,7 @@ /** - * Latte macros for Nette\Forms. + * Latte v2 macros for Nette\Forms. * * - {form name} ... {/form} * - {input name} @@ -68,9 +68,11 @@ public function macroForm(MacroNode $node, PhpWriter $writer) $node->tokenizer->reset(); return $writer->write( 'echo Nette\Bridges\FormsLatte\Runtime::renderFormBegin($form = $this->global->formsStack[] = ' - . ($name[0] === '$' ? 'is_object(%node.word) ? %node.word : ' : '') - . '$this->global->uiControl[%node.word], %node.array)' - . " /* line $node->startLine */;" + . ($name[0] === '$' + ? 'is_object($ʟ_tmp = %node.word) ? $ʟ_tmp : $this->global->uiControl[$ʟ_tmp]' + : '$this->global->uiControl[%node.word]') + . ', %node.array)' + . " /* line $node->startLine */;", ); } @@ -96,9 +98,10 @@ public function macroFormContext(MacroNode $node, PhpWriter $writer) $node->tokenizer->reset(); return $writer->write( '$form = $this->global->formsStack[] = ' - . ($name[0] === '$' ? 'is_object(%node.word) ? %node.word : ' : '') - . '$this->global->uiControl[%node.word]' - . " /* line $node->startLine */;" + . ($name[0] === '$' + ? 'is_object($ʟ_tmp = %node.word) ? $ʟ_tmp : $this->global->uiControl[$ʟ_tmp]' + : '$this->global->uiControl[%node.word]') + . " /* line $node->startLine */;", ); } @@ -120,9 +123,10 @@ public function macroFormContainer(MacroNode $node, PhpWriter $writer) $node->tokenizer->reset(); return $writer->write( '$this->global->formsStack[] = $formContainer = ' - . ($name[0] === '$' ? 'is_object(%node.word) ? %node.word : ' : '') - . 'end($this->global->formsStack)[%node.word]' - . " /* line $node->startLine */;" + . ($name[0] === '$' + ? 'is_object($ʟ_tmp = %node.word) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]' + : 'end($this->global->formsStack)[%node.word]') + . " /* line $node->startLine */;", ); } @@ -145,13 +149,13 @@ public function macroLabel(MacroNode $node, PhpWriter $writer) $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' + ? '$ʟ_input = is_object($ʟ_tmp = %0.word) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]; 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()' + $words ? ('getLabelPart(' . implode(', ', array_map([$writer, 'formatWord'], $words)) . ')') : 'getLabel()', ); } @@ -185,12 +189,14 @@ public function macroInput(MacroNode $node, PhpWriter $writer) $node->replaced = true; $name = array_shift($words); return $writer->write( - ($name[0] === '$' ? '$ʟ_input = $_input = is_object(%0.word) ? %0.word : end($this->global->formsStack)[%0.word]; echo $ʟ_input' : 'echo end($this->global->formsStack)[%0.word]') - . '->%1.raw' + ($name[0] === '$' + ? '$ʟ_input = $_input = is_object($ʟ_tmp = %word) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]; echo $ʟ_input' + : 'echo end($this->global->formsStack)[%word]') + . '->%raw' . ($node->tokenizer->isNext() ? '->addAttributes(%node.array)' : '') . " /* line $node->startLine */;", $name, - $words ? 'getControlPart(' . implode(', ', array_map([$writer, 'formatWord'], $words)) . ')' : 'getControl()' + $words ? 'getControlPart(' . implode(', ', array_map([$writer, 'formatWord'], $words)) . ')' : 'getControl()', ); } @@ -217,24 +223,29 @@ public function macroNameAttr(MacroNode $node, PhpWriter $writer) if ($tagName === 'form') { $node->openingCode = $writer->write( 'global->formsStack[] = ' - . ($name[0] === '$' ? 'is_object(%0.word) ? %0.word : ' : '') - . "\$this->global->uiControl[%0.word] /* line $node->startLine */; ?>", - $name + . ($name[0] === '$' + ? 'is_object($ʟ_tmp = %0.word) ? $ʟ_tmp : $this->global->uiControl[$ʟ_tmp]' + : '$this->global->uiControl[%0.word]') + . " /* line $node->startLine */; ?>", + $name, ); return $writer->write( 'echo Nette\Bridges\FormsLatte\Runtime::renderFormBegin(end($this->global->formsStack), %0.var, false)', - array_fill_keys($definedHtmlAttributes, null) + array_fill_keys($definedHtmlAttributes, null), ); } else { $method = $tagName === 'label' ? 'getLabel' : 'getControl'; return $writer->write( - '$ʟ_input = $_input = ' . ($name[0] === '$' ? 'is_object(%0.word) ? %0.word : ' : '') - . 'end($this->global->formsStack)[%0.word]; echo $ʟ_input->%1.raw' + '$ʟ_input = $_input = ' + . ($name[0] === '$' + ? 'is_object($ʟ_tmp = %0.word) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]' + : 'end($this->global->formsStack)[%0.word]') + . '; echo $ʟ_input->%1.raw' . ($definedHtmlAttributes ? '->addAttributes(%2.var)' : '') . '->attributes()' . " /* line $node->startLine */;", $name, $method . 'Part(' . implode(', ', array_map([$writer, 'formatWord'], $words)) . ')', - array_fill_keys($definedHtmlAttributes, null) + array_fill_keys($definedHtmlAttributes, null), ); } } @@ -285,9 +296,9 @@ public function macroInputError(MacroNode $node, PhpWriter $writer) return $writer->write("echo %escape(\$ʟ_input->getError()) /* line $node->startLine */;"); } elseif ($name[0] === '$') { return $writer->write( - '$ʟ_input = is_object(%0.word) ? %0.word : end($this->global->formsStack)[%0.word];' + '$ʟ_input = is_object($ʟ_tmp = %0.word) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp];' . "echo %escape(\$ʟ_input->getError()) /* line $node->startLine */;", - $name + $name, ); } else { return $writer->write("echo %escape(end(\$this->global->formsStack)[%0.word]->getError()) /* line $node->startLine */;", $name); @@ -310,7 +321,7 @@ public function macroFormPrint(MacroNode $node, PhpWriter $writer) return $writer->write( 'Nette\Bridges\FormsLatte\Runtime::render' . $node->name . '(' . ($name[0] === '$' ? 'is_object(%node.word) ? %node.word : ' : '') - . '$this->global->uiControl[%node.word]); exit;' + . '$this->global->uiControl[%node.word]); exit;', ); } } diff --git a/src/Bridges/FormsLatte/FormsExtension.php b/src/Bridges/FormsLatte/FormsExtension.php new file mode 100644 index 000000000..71fd693d1 --- /dev/null +++ b/src/Bridges/FormsLatte/FormsExtension.php @@ -0,0 +1,34 @@ + [Nodes\FormNode::class, 'create'], + 'formContext' => [Nodes\FormNode::class, 'create'], + 'formContainer' => [Nodes\FormContainerNode::class, 'create'], + 'label' => [Nodes\LabelNode::class, 'create'], + 'input' => [Nodes\InputNode::class, 'create'], + 'inputError' => [Nodes\InputErrorNode::class, 'create'], + 'formPrint' => [Nodes\FormPrintNode::class, 'create'], + 'formClassPrint' => [Nodes\FormPrintNode::class, 'create'], + 'n:name' => [Nodes\NNameNode::class, 'create'], + ]; + } +} diff --git a/src/Bridges/FormsLatte/Nodes/FormContainerNode.php b/src/Bridges/FormsLatte/Nodes/FormContainerNode.php new file mode 100644 index 000000000..287c32419 --- /dev/null +++ b/src/Bridges/FormsLatte/Nodes/FormContainerNode.php @@ -0,0 +1,65 @@ + */ + public static function create(Tag $tag): \Generator + { + $tag->outputMode = $tag::OutputRemoveIndentation; + $tag->expectArguments(); + + $node = new static; + $node->name = $tag->parser->parseUnquotedStringOrExpression(); + [$node->content] = yield; + return $node; + } + + + public function print(PrintContext $context): string + { + return $context->format( + '$this->global->formsStack[] = $formContainer = ' + . ($this->name instanceof StringNode + ? 'end($this->global->formsStack)[%raw]' + : 'is_object($ʟ_tmp = %raw) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]') + . '%line;' + . '%raw' + . 'array_pop($this->global->formsStack); $formContainer = end($this->global->formsStack);' + . "\n\n", + $this->name, + $this->position, + $this->content, + ); + } + + + public function &getIterator(): \Generator + { + yield $this->name; + yield $this->content; + } +} diff --git a/src/Bridges/FormsLatte/Nodes/FormNode.php b/src/Bridges/FormsLatte/Nodes/FormNode.php new file mode 100644 index 000000000..b739278a0 --- /dev/null +++ b/src/Bridges/FormsLatte/Nodes/FormNode.php @@ -0,0 +1,92 @@ + */ + public static function create(Tag $tag): \Generator + { + if ($tag->isNAttribute()) { + throw new CompileException('Did you mean
?', $tag->position); + } + + $tag->outputMode = $tag::OutputKeepIndentation; + $tag->expectArguments(); + $node = new static; + $node->name = $tag->parser->parseUnquotedStringOrExpression(); + $tag->parser->stream->tryConsume(','); + $node->attributes = $tag->parser->parseArguments(); + $node->print = $tag->name === 'form'; + + [$node->content, $endTag] = yield; + $node->endLine = $endTag?->position; + if ($endTag && $node->name instanceof StringNode) { + $endTag->parser->stream->tryConsume($node->name->value); + } + + return $node; + } + + + public function print(PrintContext $context): string + { + return $context->format( + '$form = $this->global->formsStack[] = ' + . ($this->name instanceof StringNode + ? '$this->global->uiControl[%raw]' + : 'is_object($ʟ_tmp = %raw) ? $ʟ_tmp : $this->global->uiControl[$ʟ_tmp]') + . ' %2.line;' + . ($this->print + ? 'echo Nette\Bridges\FormsLatte\Runtime::renderFormBegin($form, %raw) %2.line;' + : '') + . ' %3.raw' + . ($this->print + ? 'echo Nette\Bridges\FormsLatte\Runtime::renderFormEnd(array_pop($this->global->formsStack))' + : 'array_pop($this->global->formsStack)') + . " %4.line;\n\n", + $this->name, + $this->attributes, + $this->position, + $this->content, + $this->endLine, + ); + } + + + public function &getIterator(): \Generator + { + yield $this->name; + yield $this->attributes; + yield $this->content; + } +} diff --git a/src/Bridges/FormsLatte/Nodes/FormPrintNode.php b/src/Bridges/FormsLatte/Nodes/FormPrintNode.php new file mode 100644 index 000000000..0e3683487 --- /dev/null +++ b/src/Bridges/FormsLatte/Nodes/FormPrintNode.php @@ -0,0 +1,63 @@ +name = $tag->parser->isEnd() + ? null + : $tag->parser->parseUnquotedStringOrExpression(); + $node->mode = $tag->name; + return $node; + } + + + public function print(PrintContext $context): string + { + return $context->format( + 'Nette\Bridges\FormsLatte\Runtime::render%raw(' + . match (true) { + !$this->name => 'end($this->global->formsStack)', + $this->name instanceof StringNode => '$this->global->uiControl[%raw]', + default => 'is_object($ʟ_tmp = %raw) ? $ʟ_tmp : $this->global->uiControl[$ʟ_tmp]', + } + . ') %line; exit;', + $this->mode, + $this->name, + $this->position, + ); + } + + + public function &getIterator(): \Generator + { + if ($this->name) { + yield $this->name; + } + } +} diff --git a/src/Bridges/FormsLatte/Nodes/InputErrorNode.php b/src/Bridges/FormsLatte/Nodes/InputErrorNode.php new file mode 100644 index 000000000..f5e00eeec --- /dev/null +++ b/src/Bridges/FormsLatte/Nodes/InputErrorNode.php @@ -0,0 +1,67 @@ +outputMode = $tag::OutputKeepIndentation; + $node = new static; + $node->name = $tag->parser->isEnd() + ? null + : $tag->parser->parseUnquotedStringOrExpression(); + return $node; + } + + + public function print(PrintContext $context): string + { + if (!$this->name) { + return $context->format('echo %escape($ʟ_input->getError()) %line;', $this->position); + + } elseif ($this->name instanceof StringNode) { + return $context->format( + 'echo %escape(end($this->global->formsStack)[%raw]->getError()) %line;', + $this->name, + $this->position, + ); + + } else { + return $context->format( + '$ʟ_input = is_object($ʟ_tmp = %raw) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp];' + . 'echo %escape($ʟ_input->getError()) %line;', + $this->name, + $this->position, + ); + } + } + + + public function &getIterator(): \Generator + { + if ($this->name) { + yield $this->name; + } + } +} diff --git a/src/Bridges/FormsLatte/Nodes/InputNode.php b/src/Bridges/FormsLatte/Nodes/InputNode.php new file mode 100644 index 000000000..7efce8e45 --- /dev/null +++ b/src/Bridges/FormsLatte/Nodes/InputNode.php @@ -0,0 +1,73 @@ +outputMode = $tag::OutputKeepIndentation; + $tag->expectArguments(); + + $node = new static; + $node->name = $tag->parser->parseUnquotedStringOrExpression(colon: false); + if ($tag->parser->stream->tryConsume(':') && !$tag->parser->stream->is(',')) { + $node->part = $tag->parser->isEnd() + ? new StringNode('') + : $tag->parser->parseUnquotedStringOrExpression(); + } + $tag->parser->stream->tryConsume(','); + $node->attributes = $tag->parser->parseArguments(); + return $node; + } + + + public function print(PrintContext $context): string + { + return $context->format( + ($this->name instanceof StringNode + ? 'echo end($this->global->formsStack)[%raw]->' + : '$ʟ_input = is_object($ʟ_tmp = %raw) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]; echo $ʟ_input->') + . ($this->part ? ('getControlPart(%raw)') : 'getControl()') + . ($this->attributes->items ? '->addAttributes(%2.raw)' : '') + . ' %3.line;', + $this->name, + $this->part, + $this->attributes, + $this->position, + ); + } + + + public function &getIterator(): \Generator + { + yield $this->name; + if ($this->part) { + yield $this->part; + } + yield $this->attributes; + } +} diff --git a/src/Bridges/FormsLatte/Nodes/LabelNode.php b/src/Bridges/FormsLatte/Nodes/LabelNode.php new file mode 100644 index 000000000..81d5bb287 --- /dev/null +++ b/src/Bridges/FormsLatte/Nodes/LabelNode.php @@ -0,0 +1,92 @@ + */ + public static function create(Tag $tag): \Generator + { + if ($tag->isNAttribute()) { + throw new CompileException('Did you mean
+ global->formsStack)['username']; + echo $ʟ_input->getControlPart()->addAttributes(['value' => null, 'type' => null, 'class' => null])->attributes() /* line %d% */; + echo '> + + global->formsStack)[$ʟ_tmp]; + echo $ʟ_input->getLabelPart()->attributes() /* line %d% */; + echo '> + global->formsStack)[$ʟ_tmp]; + echo $ʟ_input->getLabelPart()->attributes() /* line %d% */; + echo '>'; + echo $ʟ_input->getLabelPart()->getHtml() /* line %d% */; + echo ' + global->formsStack)[$ʟ_tmp]; + echo $ʟ_input->getControlPart()->attributes() /* line %d% */; + echo '> + + '; + if ($ʟ_label = end($this->global->formsStack)['my']->getLabel()) echo $ʟ_label /* line %d% */; + echo end($this->global->formsStack)['my']->getControl() /* line %d% */; + echo "\n"; + echo Nette\Bridges\FormsLatte\Runtime::renderFormEnd(array_pop($this->global->formsStack)) /* line %d% */; + + echo ' + + +'; + $form = $this->global->formsStack[] = $this->global->uiControl['myForm'] /* line %d% */; + echo Nette\Bridges\FormsLatte\Runtime::renderFormBegin($form, []) /* line %d% */; + echo Nette\Bridges\FormsLatte\Runtime::renderFormEnd(array_pop($this->global->formsStack)) /* line %d% */; + + echo ' + +'; + $form = $this->global->formsStack[] = $this->global->uiControl['myForm'] /* line %d% */; + echo Nette\Bridges\FormsLatte\Runtime::renderFormBegin($form, []) /* line %d% */; + echo "\n"; + $iterations = 0; + foreach ($form['sex']->items as $key => $label) /* line %d% */ { + echo ' '; + if ($ʟ_label = end($this->global->formsStack)['sex']->getLabelPart($key)) echo $ʟ_label->startTag() /* line %d% */; + echo ' '; + echo end($this->global->formsStack)['sex']->getControlPart($key) /* line %d% */; + echo ' '; + echo LR\Filters::escapeHtmlText($label) /* line %d% */; + if ($ʟ_label) echo $ʟ_label->endTag() /* line %d% */; + echo ' + global->formsStack)['sex']; + echo $ʟ_input->getLabelPart($key)->addAttributes(['title' => null])->attributes() /* line %d% */; + echo ' title=hello> global->formsStack)['sex']; + echo $ʟ_input->getControlPart($key)->attributes() /* line %d% */; + echo '> +'; + $iterations++; + } + + echo 'global->formsStack)['sex']; + echo $ʟ_input->getLabelPart()->attributes() /* line %d% */; + echo '> +global->formsStack)['sex']; + echo $ʟ_input->getLabelPart()->attributes() /* line %d% */; + echo '>'; + echo $ʟ_input->getLabelPart()->getHtml() /* line %d% */; + echo ' +global->formsStack)['sex']; + echo $ʟ_input->getLabelPart()->addAttributes(['title' => null])->attributes() /* line %d% */; + echo ' title="hello">'; + echo $ʟ_input->getLabelPart()->getHtml() /* line %d% */; + echo ' +global->formsStack)[$ʟ_tmp]; + echo $ʟ_input->getControlPart("{$key}")->attributes() /* line %d% */; + echo '> + + +'; + if ($ʟ_label = end($this->global->formsStack)['checkbox']->getLabelPart('')) echo $ʟ_label->startTag() /* line %d% */; + echo ' '; + echo end($this->global->formsStack)['checkbox']->getControlPart('') /* line %d% */; + echo ' Label'; + if ($ʟ_label) echo $ʟ_label->endTag() /* line %d% */; + echo ' +global->formsStack)['checkbox']; + echo $ʟ_input->getLabelPart('')->addAttributes(['title' => null])->attributes() /* line %d% */; + echo ' title=hello> global->formsStack)['checkbox']; + echo $ʟ_input->getControlPart('')->attributes() /* line %d% */; + echo '> +global->formsStack)['checkbox']; + echo $ʟ_input->getLabelPart()->addAttributes(['title' => null])->attributes() /* line %d% */; + echo ' title=hello> global->formsStack)['checkbox']; + echo $ʟ_input->getControlPart()->attributes() /* line %d% */; + echo '> +global->formsStack)['checkbox']; + echo $ʟ_input->getLabelPart('')->attributes() /* line %d% */; + echo '>'; + echo $ʟ_input->getLabelPart()->getHtml() /* line %d% */; + echo ' +global->formsStack)['checkbox']; + echo $ʟ_input->getLabelPart()->addAttributes(['title' => null])->attributes() /* line %d% */; + echo ' title=hello>'; + echo $ʟ_input->getLabelPart()->getHtml() /* line %d% */; + echo ' + + +'; + $iterations = 0; + foreach ($form['checklist']->items as $key => $label) /* line %d% */ { + echo ' '; + if ($ʟ_label = end($this->global->formsStack)['checklist']->getLabelPart($key)) echo $ʟ_label->startTag() /* line %d% */; + echo ' '; + echo end($this->global->formsStack)['checklist']->getControlPart($key) /* line %d% */; + echo ' '; + echo LR\Filters::escapeHtmlText($label) /* line %d% */; + if ($ʟ_label) echo $ʟ_label->endTag() /* line %d% */; + echo ' + global->formsStack)['checklist']; + echo $ʟ_input->getLabelPart($key)->attributes() /* line %d% */; + echo '> global->formsStack)['checklist']; + echo $ʟ_input->getControlPart($key)->addAttributes(['title' => null])->attributes() /* line %d% */; + echo ' title=hello> +'; + $iterations++; + } + + echo 'global->formsStack)['checklist']; + echo $ʟ_input->getLabelPart()->attributes() /* line %d% */; + echo '> +global->formsStack)['checklist']; + echo $ʟ_input->getLabelPart()->attributes() /* line %d% */; + echo '>'; + echo $ʟ_input->getLabelPart()->getHtml() /* line %d% */; + echo ' +global->formsStack)['checklist']; + echo $ʟ_input->getLabelPart()->addAttributes(['title' => null])->attributes() /* line %d% */; + echo ' title="hello">'; + echo $ʟ_input->getLabelPart()->getHtml() /* line %d% */; + echo ' + + +'; + if (1) /* line %d% */ { + echo 'global->formsStack[] = $this->global->uiControl['myForm'] /* line %d% */; + echo Nette\Bridges\FormsLatte\Runtime::renderFormBegin(end($this->global->formsStack), ['id' => null, 'class' => null], false) /* line %d% */; + echo ' id="myForm" class="ajax"> + global->formsStack)['username']; + echo $ʟ_input->getControlPart()->attributes() /* line %d% */; + echo '> +'; + echo Nette\Bridges\FormsLatte\Runtime::renderFormEnd(array_pop($this->global->formsStack), false) /* line %d% */; + echo ' +'; + } + echo ' + +global->formsStack[] = $this->global->uiControl['myForm'] /* line %d% */; + echo Nette\Bridges\FormsLatte\Runtime::renderFormBegin(end($this->global->formsStack), ['class' => null], false) /* line %d% */; + echo ($ʟ_tmp = array_filter(['nclass'])) ? ' class="' . LR\Filters::escapeHtmlAttr(implode(" ", array_unique($ʟ_tmp))) . '"' : "" /* line %d% */; + echo '> + global->formsStack)['username']; + echo $ʟ_input->getControlPart()->addAttributes(['class' => null])->attributes() /* line %d% */; + echo ($ʟ_tmp = array_filter(['nclass'])) ? ' class="' . LR\Filters::escapeHtmlAttr(implode(" ", array_unique($ʟ_tmp))) . '"' : "" /* line %d% */; + echo '> +'; + echo Nette\Bridges\FormsLatte\Runtime::renderFormEnd(array_pop($this->global->formsStack), false) /* line %d% */; + echo ' + + +global->formsStack[] = is_object($ʟ_tmp = $this->global->uiControl['myForm']) ? $ʟ_tmp : $this->global->uiControl[$ʟ_tmp] /* line %d% */; + echo Nette\Bridges\FormsLatte\Runtime::renderFormBegin(end($this->global->formsStack), [], false) /* line %d% */; + echo '> + global->formsStack)['username']; + echo $ʟ_input->getControlPart()->attributes() /* line %d% */; + echo '> +'; + echo Nette\Bridges\FormsLatte\Runtime::renderFormEnd(array_pop($this->global->formsStack), false) /* line %d% */; + echo ' + + +global->formsStack)['select']; + echo $ʟ_input->getControlPart()->attributes() /* line %d% */; + echo '>'; + echo $ʟ_input->getControl()->getHtml() /* line %d% */; + echo ' + + +global->formsStack)['area']; + echo $ʟ_input->getControlPart()->addAttributes(['title' => null])->attributes() /* line %d% */; + echo ' title="'; + echo LR\Filters::escapeHtmlAttr(10) /* line %d% */; + echo '">'; + echo $ʟ_input->getControl()->getHtml() /* line %d% */; + echo ' + + +global->formsStack)['select']; + echo $ʟ_input->getControlPart()->attributes() /* line %d% */; + echo '>'; + echo $ʟ_input->getControl()->getHtml() /* line %d% */; + echo ' +'; + echo Nette\Bridges\FormsLatte\Runtime::renderFormEnd(array_pop($this->global->formsStack)) /* line %d% */; + + echo ' + + +'; + $form = $this->global->formsStack[] = $this->global->uiControl['myForm'] /* line %d% */; + echo ' +global->formsStack)['sex']; + echo $ʟ_input->getLabelPart()->attributes() /* line %d% */; + echo '>'; + echo $ʟ_input->getLabelPart()->getHtml() /* line %d% */; + echo ' +global->formsStack)['username']; + echo $ʟ_input->getControlPart()->attributes() /* line %d% */; + echo '> +'; + array_pop($this->global->formsStack) /* line %d% */; +%A% diff --git a/tests/Forms.Latte/forms.button.phpt b/tests/Forms.Latte/forms.button.phpt new file mode 100644 index 000000000..a1cb3ac94 --- /dev/null +++ b/tests/Forms.Latte/forms.button.phpt @@ -0,0 +1,31 @@ +addSubmit('send', 'Sign in'); + +$latte = new Latte\Engine; +$latte->addExtension(new FormsExtension); +$latte->addProvider('uiControl', ['myForm' => $form]); + +Assert::matchFile( + __DIR__ . '/expected/forms.button.phtml', + $latte->compile(__DIR__ . '/templates/forms.button.latte') +); + +Assert::matchFile( + __DIR__ . '/expected/forms.button.html', + $latte->renderToString(__DIR__ . '/templates/forms.button.latte') +); diff --git a/tests/Forms.Latte/forms.error.phpt b/tests/Forms.Latte/forms.error.phpt new file mode 100644 index 000000000..01f7e7d6a --- /dev/null +++ b/tests/Forms.Latte/forms.error.phpt @@ -0,0 +1,51 @@ +setLoader(new Latte\Loaders\StringLoader); +$latte->addExtension(new FormsExtension); + +Assert::exception(function () use ($latte) { + $latte->compile('
'); +}, Latte\CompileException::class, 'Did you mean
? (at column 7)'); + +Assert::exception(function () use ($latte) { + $latte->compile('
'); +}, Latte\CompileException::class, 'Missing arguments in n:name (at column 7)'); + +Assert::exception(function () use ($latte) { + $latte->compile('
'); +}, Latte\CompileException::class, 'Unexpected attribute n:inner-name, did you mean n:inner-label? (at column 7)'); + + +Assert::exception(function () use ($latte) { + $latte->compile('{form /}'); +}, Latte\CompileException::class, 'Missing arguments in {form} (at column 7)'); + +Assert::exception(function () use ($latte) { + $latte->compile('{formContainer /}'); +}, Latte\CompileException::class, 'Missing arguments in {formContainer} (at column 7)'); + + +Assert::exception(function () use ($latte) { + $latte->compile('{label /}'); +}, Latte\CompileException::class, 'Missing arguments in {label} (at column 7)'); + +Assert::exception(function () use ($latte) { + $latte->compile('{input /}'); +}, Latte\CompileException::class, 'Missing arguments in {input} (at column 7)'); + +Assert::exception(function () use ($latte) { + $latte->compile('{name /}'); +}, Latte\CompileException::class, 'Unexpected tag {name} (at column 7)'); diff --git a/tests/Forms.Latte/forms.formContainer.phpt b/tests/Forms.Latte/forms.formContainer.phpt new file mode 100644 index 000000000..fc5b3a901 --- /dev/null +++ b/tests/Forms.Latte/forms.formContainer.phpt @@ -0,0 +1,51 @@ +addText('input1', 'Input 1'); + +$cont1 = $form->addContainer('cont1'); +$cont1->addText('input2', 'Input 2'); +$cont1->addText('input3', 'Input 3'); + +$cont2 = $cont1->addContainer('cont2'); +$cont2->addCheckbox('input4', 'Input 4'); +$cont2->addCheckbox('input5', 'Input 5'); +$cont2->addCheckbox('input6', 'Input 6'); + +$cont1->addText('input7', 'Input 7'); + +$contItems = $form->addContainer('items'); +$items = [1, 3]; +foreach ($items as $item) { + $contItem = $contItems->addContainer($item); + $contItem->addText('input', 'Input'); +} + +$form->addSubmit('input8', 'Input 8'); + + +$latte = new Latte\Engine; +$latte->addExtension(new FormsExtension); +$latte->addProvider('uiControl', ['myForm' => $form]); + +Assert::matchFile( + __DIR__ . '/expected/forms.formContainer.phtml', + $latte->compile(__DIR__ . '/templates/forms.formContainer.latte') +); +Assert::matchFile( + __DIR__ . '/expected/forms.formContainer.html', + $latte->renderToString(__DIR__ . '/templates/forms.formContainer.latte') +); diff --git a/tests/Forms.Latte/forms.get.phpt b/tests/Forms.Latte/forms.get.phpt new file mode 100644 index 000000000..76f253873 --- /dev/null +++ b/tests/Forms.Latte/forms.get.phpt @@ -0,0 +1,32 @@ +setMethod('get'); +$form->setAction('?arg=val'); +$form->addSubmit('send', 'Sign in'); + +$latte = new Latte\Engine; +$latte->addExtension(new FormsExtension); +$latte->addProvider('uiControl', ['myForm' => $form]); + +Assert::matchFile( + __DIR__ . '/expected/forms.get.phtml', + $latte->compile(__DIR__ . '/templates/forms.get.latte') +); +Assert::matchFile( + __DIR__ . '/expected/forms.get.html', + $latte->renderToString(__DIR__ . '/templates/forms.get.latte') +); diff --git a/tests/Forms.Latte/forms.phpt b/tests/Forms.Latte/forms.phpt new file mode 100644 index 000000000..86cab7ff1 --- /dev/null +++ b/tests/Forms.Latte/forms.phpt @@ -0,0 +1,57 @@ +My'; + } + + + public function getControl() + { + return ''; + } +} + + +$form = new Form; +$form->getElementPrototype()->addClass('form-class'); +$form->addHidden('id'); +$form->addText('username', 'Username:'); // must have just one textfield to generate IE fix +$form['username']->getControlPrototype()->addClass('control-class'); +$form->addRadioList('sex', 'Sex:', ['m' => 'male', 'f' => 'female']); +$form->addSelect('select', null, ['m' => 'male', 'f' => 'female']); +$form->addTextArea('area', null)->setValue('oneaddCheckbox('checkbox', 'Checkbox'); +$form->addCheckboxList('checklist', 'CheckboxList:', ['m' => 'male', 'f' => 'female']); +$form->addSubmit('send', 'Sign in'); +$form['my'] = new MyControl; + +$latte = new Latte\Engine; +$latte->addExtension(new FormsExtension); +$latte->addProvider('uiControl', ['myForm' => $form]); + +$form['username']->addError('error'); + +Assert::matchFile( + __DIR__ . '/expected/forms.phtml', + $latte->compile(__DIR__ . '/templates/forms.latte') +); +Assert::matchFile( + __DIR__ . '/expected/forms.html', + $latte->renderToString(__DIR__ . '/templates/forms.latte') +); diff --git a/tests/Forms.Latte/templates/forms.formContainer.latte b/tests/Forms.Latte/templates/forms.formContainer.latte index 5abd962bc..cbc69cbb9 100644 --- a/tests/Forms.Latte/templates/forms.formContainer.latte +++ b/tests/Forms.Latte/templates/forms.formContainer.latte @@ -16,7 +16,7 @@ Checkboxes -
    +
    1. {input $field}
    diff --git a/tests/Forms.Latte/templates/forms.latte b/tests/Forms.Latte/templates/forms.latte index 0c4f5bc38..f4f3604f8 100644 --- a/tests/Forms.Latte/templates/forms.latte +++ b/tests/Forms.Latte/templates/forms.latte @@ -11,7 +11,7 @@ {inputError $form[$name]} {/foreach} - {label $form[username]} + {label $form[username] /} @@ -35,6 +35,7 @@ global->formsStack)[$form['username']]; + $ʟ_input = $_input = is_object($ʟ_tmp = $form['username']) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]; echo $ʟ_input->getControlPart()->attributes() /* line 21 */; echo '> @@ -229,7 +229,7 @@ '; - $form = $this->global->formsStack[] = is_object($this->global->uiControl['myForm']) ? $this->global->uiControl['myForm'] : $this->global->uiControl[$this->global->uiControl['myForm']] /* line 68 */; + $form = $this->global->formsStack[] = is_object($ʟ_tmp = $this->global->uiControl['myForm']) ? $ʟ_tmp : $this->global->uiControl[$ʟ_tmp] /* line 68 */; echo 'global->formsStack), [], false); echo '> diff --git a/tests/Forms.Latte2/expected/FormMacros.get.html b/tests/Forms.Latte2/expected/FormMacros.get.html new file mode 100644 index 000000000..bf19a13ea --- /dev/null +++ b/tests/Forms.Latte2/expected/FormMacros.get.html @@ -0,0 +1,7 @@ +
    +
    + + +
    + +
    diff --git a/tests/Forms.Latte/expected/FormMacros.get.phtml b/tests/Forms.Latte2/expected/FormMacros.get.phtml similarity index 100% rename from tests/Forms.Latte/expected/FormMacros.get.phtml rename to tests/Forms.Latte2/expected/FormMacros.get.phtml diff --git a/tests/Forms.Latte2/templates/forms.button.latte b/tests/Forms.Latte2/templates/forms.button.latte new file mode 100644 index 000000000..8e9969b37 --- /dev/null +++ b/tests/Forms.Latte2/templates/forms.button.latte @@ -0,0 +1,9 @@ +
    + + + + +