Skip to content

Commit f4f6a43

Browse files
authored
Validate slug/action/alias name (#470)
Validation rules added using the `addRule(Form::Pattern, ...)` call, not using any custom validation callback because I want the JS validation to be also used and don't want to add a custom one.
2 parents 2b5228d + 19b5e9c commit f4f6a43

File tree

8 files changed

+97
-7
lines changed

8 files changed

+97
-7
lines changed

app/config/services.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ services:
4848
- MichalSpacekCz\Form\Controls\TrainingControlsFactory
4949
- MichalSpacekCz\Form\DeletePersonalDataFormFactory
5050
- MichalSpacekCz\Form\FormFactory
51+
- MichalSpacekCz\Form\FormValidators
5152
- MichalSpacekCz\Form\InterviewFormFactory(videoThumbnails: @interviewVideoThumbnails)
5253
- MichalSpacekCz\Form\PostFormFactory
5354
- MichalSpacekCz\Form\Pulse\PasswordsStorageAlgorithmFormFactory

app/src/Form/FormValidators.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace MichalSpacekCz\Form;
5+
6+
use Contributte\Translation\Translator;
7+
use Nette\Forms\Controls\TextInput;
8+
use Nette\Forms\Form;
9+
10+
readonly class FormValidators
11+
{
12+
13+
public function __construct(
14+
private Translator $translator,
15+
) {
16+
}
17+
18+
19+
public function addValidateSlugRules(TextInput $input): void
20+
{
21+
$input->addRule(Form::Pattern, $this->translator->translate('messages.forms.validateSlugParamsError'), '[a-z0-9.,_-]+');
22+
}
23+
24+
}

app/src/Form/PostFormFactory.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
public function __construct(
3838
private FormFactory $factory,
39+
private FormValidators $validators,
3940
private Translator $translator,
4041
private BlogPosts $blogPosts,
4142
private BlogPostFactory $blogPostFactory,
@@ -62,8 +63,9 @@ public function create(callable $onSuccessAdd, callable $onSuccessEdit, DefaultT
6263
$form->addText('title', 'Titulek:')
6364
->setRequired('Zadejte prosím titulek')
6465
->addRule(Form::MinLength, 'Titulek musí mít alespoň %d znaky', 3);
65-
$form->addText('slug', 'Slug:')
66+
$slugInput = $form->addText('slug', 'Slug:')
6667
->addRule(Form::MinLength, 'Slug musí mít alespoň %d znaky', 3);
68+
$this->validators->addValidateSlugRules($slugInput);
6769
$this->addPublishedDate($form->addText('published', 'Vydáno:'))
6870
->setDefaultValue(date('Y-m-d') . ' HH:MM');
6971
$previewKeyInput = $form->addText('previewKey', 'Klíč pro náhled:')
@@ -152,8 +154,9 @@ function () use ($form, $post): BlogPost {
152154
$newPost = $this->buildPost($values, $post?->getId());
153155
try {
154156
if ($post) {
155-
assert(is_string($values->editSummary));
156-
$this->blogPosts->update($newPost, $values->editSummary === '' ? null : $values->editSummary, $post->getSlugTags());
157+
$editSummary = $values->editSummary ?? null;
158+
assert($editSummary === null || is_string($editSummary));
159+
$this->blogPosts->update($newPost, $editSummary, $post->getSlugTags());
157160
$onSuccessEdit($newPost);
158161
} else {
159162
$onSuccessAdd($this->blogPosts->add($newPost));

app/src/Form/TalkFormFactory.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
public function __construct(
2222
private FormFactory $factory,
23+
private FormValidators $validators,
2324
private TrainingControlsFactory $trainingControlsFactory,
2425
private Talks $talks,
2526
private LinkGenerator $linkGenerator,
@@ -42,9 +43,10 @@ public function create(callable $onSuccess, ?Talk $talk = null): UiForm
4243
$form->addSelect('locale', 'Jazyk:', $this->locales->getAllLocales())
4344
->setRequired('Zadejte prosím jazyk')
4445
->setPrompt('- vyberte -');
45-
$form->addText('action', 'Akce:')
46+
$actionInput = $form->addText('action', 'Akce:')
4647
->setRequired(false)
4748
->addRule(Form::MaxLength, 'Maximální délka akce je %d znaků', 200);
49+
$this->validators->addValidateSlugRules($actionInput);
4850
$form->addText('title', 'Název:')
4951
->setRequired('Zadejte prosím název')
5052
->addRule(Form::MaxLength, 'Maximální délka názvu je %d znaků', 200);

app/src/Form/TalkSlidesFormFactory.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
public function __construct(
2121
private FormFactory $factory,
22+
private FormValidators $validators,
2223
private TalkSlides $talkSlides,
2324
private TexyFormatter $texyFormatter,
2425
private SupportedImageFileFormats $supportedImageFileFormats,
@@ -96,9 +97,9 @@ private function addSlideFields(UiForm $form, Container $container, ?int $filena
9697
$supportedImages = '*.' . implode(', *.', $this->supportedImageFileFormats->getMainExtensions());
9798
$supportedAlternativeImages = '*.' . implode(', *.', $this->supportedImageFileFormats->getAlternativeExtensions());
9899
$disableSlideUploads = (bool)$filenamesTalkId;
99-
$container->addText('alias', 'Alias:')
100-
->setRequired('Zadejte prosím alias')
101-
->addRule(Form::Pattern, 'Alias musí být ve formátu [_.,a-z0-9-]+', '[_.,a-z0-9-]+');
100+
$aliasInput = $container->addText('alias', 'Alias:')
101+
->setRequired('Zadejte prosím alias');
102+
$this->validators->addValidateSlugRules($aliasInput);
102103
$container->addInteger('number', 'Slajd:')
103104
->setDefaultValue(1)
104105
->setHtmlAttribute('class', 'right slide-nr')

app/src/lang/messages.cs_CZ.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,3 +396,5 @@ cookies:
396396
netteSameSiteCheck: "Používá se pro detekci \"//same-site//\":[blog:co-znamena-origin-site-etld-etld-plus-1-public-suffix-a-psl#same-site] požadavků."
397397
httpHeaders:
398398
headerNotSent: "hlavička neposlána"
399+
forms:
400+
validateSlugParamsError: "%label musí odpovídat formátu %d"

app/src/lang/messages.en_US.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,3 +396,5 @@ cookies:
396396
netteSameSiteCheck: "Used to detect \"//same-site//\":[blog:origin-site-etld-etld-plus-one-public-suffix-psl-what-are-they#same-site] requests."
397397
httpHeaders:
398398
headerNotSent: "header not sent"
399+
forms:
400+
validateSlugParamsError: "%label must match %d"
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace MichalSpacekCz\Form;
5+
6+
use MichalSpacekCz\Test\TestCaseRunner;
7+
use Nette\Forms\Controls\TextInput;
8+
use Nette\Forms\Form;
9+
use Tester\Assert;
10+
use Tester\TestCase;
11+
12+
require __DIR__ . '/../bootstrap.php';
13+
14+
/** @testCase */
15+
class FormValidatorsTest extends TestCase
16+
{
17+
18+
public function __construct(
19+
private readonly FormValidators $validators,
20+
) {
21+
}
22+
23+
24+
/**
25+
* @return list<array{0:string, 1:bool}>
26+
*/
27+
public function getSlugs(): array
28+
{
29+
return [
30+
['foo', true],
31+
['foo-bar', true],
32+
['foo-bar.baz', true],
33+
['foo-bar,baz', true],
34+
['foo-bar_baz', true],
35+
['foo-bar-1337', true],
36+
['foo/bar', false],
37+
];
38+
}
39+
40+
41+
/** @dataProvider getSlugs */
42+
public function testAddValidateSlugRules(string $slug, bool $result): void
43+
{
44+
$input = new TextInput();
45+
$input->value = $slug;
46+
$this->validators->addValidateSlugRules($input);
47+
/** @noinspection PhpInternalEntityUsedInspection */
48+
$input->setParent(new Form());
49+
$input->validate();
50+
Assert::same($result ? [] : ['messages.forms.validateSlugParamsError'], $input->getErrors());
51+
}
52+
53+
}
54+
55+
TestCaseRunner::run(FormValidatorsTest::class);

0 commit comments

Comments
 (0)