• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / forms / 10256872480

05 Aug 2024 10:21PM UTC coverage: 93.004%. Remained the same
10256872480

push

github

dg
added HTML attribute data-nette-error

1 of 1 new or added line in 1 file covered. (100.0%)

6 existing lines in 1 file now uncovered.

2087 of 2244 relevant lines covered (93.0%)

0.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.88
/src/Forms/Controls/BaseControl.php
1
<?php
2

3
/**
4
 * This file is part of the Nette Framework (https://nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
declare(strict_types=1);
9

10
namespace Nette\Forms\Controls;
11

12
use Nette;
13
use Nette\Forms\Control;
14
use Nette\Forms\Form;
15
use Nette\Forms\Rules;
16
use Nette\Utils\Html;
17
use Stringable;
18

19

20
/**
21
 * Base class that implements the basic functionality common to form controls.
22
 *
23
 * @property-read Form $form
24
 * @property-read string $htmlName
25
 * @property   string|bool|null $htmlId
26
 * @property   mixed $value
27
 * @property   string|Stringable $caption
28
 * @property   bool $disabled
29
 * @property   bool $omitted
30
 * @property-read Html $control
31
 * @property-read Html $label
32
 * @property-read Html $controlPrototype
33
 * @property-read Html $labelPrototype
34
 * @property   bool $required
35
 * @property-read bool $filled
36
 * @property-read array $errors
37
 * @property-read array $options
38
 * @property-read string $error
39
 */
40
abstract class BaseControl extends Nette\ComponentModel\Component implements Control
41
{
42
        public static string $idMask = 'frm-%s';
43

44
        protected mixed $value = null;
45
        protected Html $control;
46
        protected Html $label;
47
        protected bool $disabled = false;
48

49
        /** @var callable[][]  extension methods */
50
        private static array $extMethods = [];
51
        private string|Stringable|null $caption;
52
        private array $errors = [];
53
        private ?bool $omitted = null;
54
        private Rules $rules;
55

56
        /** true means autodetect */
57
        private Nette\Localization\Translator|bool|null $translator = true;
58
        private array $options = [];
59

60

61
        public function __construct(string|Stringable|null $caption = null)
1✔
62
        {
63
                $this->control = Html::el('input', ['type' => null, 'name' => null]);
1✔
64
                $this->label = Html::el('label');
1✔
65
                $this->caption = $caption;
1✔
66
                $this->rules = new Rules($this);
1✔
67
                $this->setValue(null);
1✔
68
                $this->monitor(Form::class, function (Form $form): void {
1✔
69
                        if (!$this->isDisabled() && $form->isAnchored() && $form->isSubmitted()) {
1✔
70
                                $this->loadHttpData();
1✔
71
                        }
72
                });
1✔
73
        }
1✔
74

75

76
        /**
77
         * Sets textual caption or label.
78
         */
79
        public function setCaption(string|Stringable $caption): static
80
        {
81
                $this->caption = $caption;
×
82
                return $this;
×
83
        }
84

85

86
        public function getCaption(): string|Stringable|null
87
        {
88
                return $this->caption;
1✔
89
        }
90

91

92
        /**
93
         * Returns form.
94
         * @return ($throw is true ? Form : ?Form)
95
         */
96
        public function getForm(bool $throw = true): ?Form
1✔
97
        {
98
                return $this->lookup(Form::class, $throw);
1✔
99
        }
100

101

102
        /**
103
         * Loads HTTP data.
104
         */
105
        public function loadHttpData(): void
106
        {
107
                $this->setValue($this->getHttpData(Form::DataText));
1✔
108
        }
1✔
109

110

111
        /**
112
         * Loads HTTP data.
113
         */
114
        protected function getHttpData($type, ?string $htmlTail = null): mixed
1✔
115
        {
116
                return $this->getForm()->getHttpData($type, $this->getHtmlName() . $htmlTail);
1✔
117
        }
118

119

120
        /**
121
         * Returns HTML name of control.
122
         */
123
        public function getHtmlName(): string
124
        {
125
                return $this->control->name ?? Nette\Forms\Helpers::generateHtmlName($this->lookupPath(Form::class));
1✔
126
        }
127

128

129
        /********************* interface Control ****************d*g**/
130

131

132
        /**
133
         * Sets control's value.
134
         * @internal
135
         */
136
        public function setValue($value): static
137
        {
138
                $this->value = $value;
1✔
139
                return $this;
1✔
140
        }
141

142

143
        /**
144
         * Returns control's value.
145
         * @return mixed
146
         */
147
        public function getValue(): mixed
148
        {
149
                return $this->value;
1✔
150
        }
151

152

153
        /**
154
         * Is control filled?
155
         */
156
        public function isFilled(): bool
157
        {
158
                $value = $this->getValue();
1✔
159
                return $value !== null && $value !== [] && $value !== '';
1✔
160
        }
161

162

163
        /**
164
         * Sets control's default value.
165
         */
166
        public function setDefaultValue($value): static
167
        {
168
                $form = $this->getForm(throw: false);
1✔
169
                if ($this->isDisabled() || !$form || !$form->isAnchored() || !$form->isSubmitted()) {
1✔
170
                        $this->setValue($value);
1✔
171
                }
172

173
                return $this;
1✔
174
        }
175

176

177
        /**
178
         * Disables or enables control.
179
         */
180
        public function setDisabled(bool $state = true): static
1✔
181
        {
182
                $this->disabled = $state;
1✔
183
                if ($state) {
1✔
184
                        $this->setValue(null);
1✔
185
                } elseif (($form = $this->getForm(throw: false)) && $form->isAnchored() && $form->isSubmitted()) {
1✔
186
                        $this->loadHttpData();
1✔
187
                }
188

189
                return $this;
1✔
190
        }
191

192

193
        /**
194
         * Is control disabled?
195
         */
196
        public function isDisabled(): bool
197
        {
198
                return $this->disabled;
1✔
199
        }
200

201

202
        /**
203
         * Sets whether control value is excluded from $form->getValues() result.
204
         */
205
        public function setOmitted(bool $state = true): static
1✔
206
        {
207
                $this->omitted = $state;
1✔
208
                return $this;
1✔
209
        }
210

211

212
        /**
213
         * Is control value excluded from $form->getValues() result?
214
         */
215
        public function isOmitted(): bool
216
        {
217
                return $this->omitted || ($this->isDisabled() && $this->omitted === null);
1✔
218
        }
219

220

221
        /********************* rendering ****************d*g**/
222

223

224
        /**
225
         * Generates control's HTML element.
226
         */
227
        public function getControl(): Html|string
228
        {
229
                $this->setOption('rendered', true);
1✔
230
                $el = clone $this->control;
1✔
231
                return $el->addAttributes([
1✔
232
                        'name' => $this->getHtmlName(),
1✔
233
                        'id' => $this->getHtmlId(),
1✔
234
                        'required' => $this->isRequired(),
1✔
235
                        'disabled' => $this->isDisabled(),
1✔
236
                        'data-nette-rules' => Nette\Forms\Helpers::exportRules($this->rules) ?: null,
1✔
237
                        'data-nette-error' => $this->hasErrors(),
1✔
238
                ]);
239
        }
240

241

242
        /**
243
         * Generates label's HTML element.
244
         */
245
        public function getLabel(string|Stringable|null $caption = null): Html|string|null
1✔
246
        {
247
                $label = clone $this->label;
1✔
248
                $label->for = $this->getHtmlId();
1✔
249
                $caption ??= $this->caption;
1✔
250
                $translator = $this->getForm()->getTranslator();
1✔
251
                $label->setText($translator && !$caption instanceof Nette\HtmlStringable ? $translator->translate($caption) : $caption);
1✔
252
                return $label;
1✔
253
        }
254

255

256
        public function getControlPart(): ?Html
257
        {
258
                return $this->getControl();
1✔
259
        }
260

261

262
        public function getLabelPart(): ?Html
263
        {
264
                return $this->getLabel();
1✔
265
        }
266

267

268
        /**
269
         * Returns control's HTML element template.
270
         */
271
        public function getControlPrototype(): Html
272
        {
273
                return $this->control;
1✔
274
        }
275

276

277
        /**
278
         * Returns label's HTML element template.
279
         */
280
        public function getLabelPrototype(): Html
281
        {
UNCOV
282
                return $this->label;
×
283
        }
284

285

286
        /**
287
         * Changes control's HTML id.
288
         */
289
        public function setHtmlId(string|bool|null $id): static
1✔
290
        {
291
                $this->control->id = $id;
1✔
292
                return $this;
1✔
293
        }
294

295

296
        /**
297
         * Returns control's HTML id.
298
         */
299
        public function getHtmlId(): string|bool|null
300
        {
301
                if (!isset($this->control->id)) {
1✔
302
                        $form = $this->getForm();
1✔
303
                        $prefix = $form instanceof Nette\Application\UI\Form || $form->getName() === null
1✔
304
                                ? ''
1✔
305
                                : $form->getName() . '-';
1✔
306
                        $this->control->id = sprintf(self::$idMask, $prefix . $this->lookupPath());
1✔
307
                }
308

309
                return $this->control->id;
1✔
310
        }
311

312

313
        /**
314
         * Changes control's HTML attribute.
315
         */
316
        public function setHtmlAttribute(string $name, mixed $value = true): static
1✔
317
        {
318
                $this->control->$name = $value;
1✔
319
                if (
320
                        $name === 'name'
1✔
321
                        && ($form = $this->getForm(false))
1✔
322
                        && !$this->isDisabled()
1✔
323
                        && $form->isAnchored()
1✔
324
                        && $form->isSubmitted()
1✔
325
                ) {
326
                        $this->loadHttpData();
1✔
327
                }
328

329
                return $this;
1✔
330
        }
331

332

333
        /**
334
         * @deprecated  use setHtmlAttribute()
335
         */
336
        public function setAttribute(string $name, mixed $value = true): static
337
        {
UNCOV
338
                return $this->setHtmlAttribute($name, $value);
×
339
        }
340

341

342
        /********************* translator ****************d*g**/
343

344

345
        /**
346
         * Sets translate adapter.
347
         */
348
        public function setTranslator(?Nette\Localization\Translator $translator): static
1✔
349
        {
350
                $this->translator = $translator;
1✔
351
                return $this;
1✔
352
        }
353

354

355
        /**
356
         * Returns translate adapter.
357
         */
358
        public function getTranslator(): ?Nette\Localization\Translator
359
        {
360
                if ($this->translator === true) {
1✔
361
                        return $this->getForm(false)
1✔
362
                                ? $this->getForm()->getTranslator()
1✔
363
                                : null;
1✔
364
                }
365

366
                return $this->translator;
1✔
367
        }
368

369

370
        /**
371
         * Returns translated string.
372
         */
373
        public function translate($value, ...$parameters): mixed
1✔
374
        {
375
                if ($translator = $this->getTranslator()) {
1✔
376
                        $tmp = is_array($value) ? [&$value] : [[&$value]];
1✔
377
                        foreach ($tmp[0] as &$v) {
1✔
378
                                if ($v != null && !$v instanceof Nette\HtmlStringable) { // intentionally ==
1✔
379
                                        $v = $translator->translate($v, ...$parameters);
1✔
380
                                }
381
                        }
382
                }
383

384
                return $value;
1✔
385
        }
386

387

388
        /********************* rules ****************d*g**/
389

390

391
        /**
392
         * Adds a validation rule.
393
         */
394
        public function addRule(
1✔
395
                callable|string $validator,
396
                string|Stringable|null $errorMessage = null,
397
                mixed $arg = null,
398
        ): static
399
        {
400
                $this->rules->addRule($validator, $errorMessage, $arg);
1✔
401
                return $this;
1✔
402
        }
403

404

405
        /**
406
         * Adds a validation condition a returns new branch.
407
         */
408
        public function addCondition($validator, $value = null): Rules
1✔
409
        {
410
                return $this->rules->addCondition($validator, $value);
1✔
411
        }
412

413

414
        /**
415
         * Adds a validation condition based on another control a returns new branch.
416
         */
417
        public function addConditionOn(Control $control, $validator, $value = null): Rules
1✔
418
        {
419
                return $this->rules->addConditionOn($control, $validator, $value);
1✔
420
        }
421

422

423
        /**
424
         * Adds an input filter callback.
425
         */
426
        public function addFilter(callable $filter): static
1✔
427
        {
428
                $this->getRules()->addFilter($filter);
1✔
429
                return $this;
1✔
430
        }
431

432

433
        public function getRules(): Rules
434
        {
435
                return $this->rules;
1✔
436
        }
437

438

439
        /**
440
         * Makes control mandatory.
441
         */
442
        public function setRequired(string|Stringable|bool $value = true): static
1✔
443
        {
444
                $this->rules->setRequired($value);
1✔
445
                return $this;
1✔
446
        }
447

448

449
        /**
450
         * Is control mandatory?
451
         */
452
        public function isRequired(): bool
453
        {
454
                return $this->rules->isRequired();
1✔
455
        }
456

457

458
        /**
459
         * Performs the server side validation.
460
         */
461
        public function validate(): void
462
        {
463
                if ($this->isDisabled()) {
1✔
464
                        return;
1✔
465
                }
466

467
                $this->cleanErrors();
1✔
468
                $this->rules->validate();
1✔
469
        }
1✔
470

471

472
        /**
473
         * Adds error message to the list.
474
         */
475
        public function addError(string|Stringable $message, bool $translate = true): void
1✔
476
        {
477
                $this->errors[] = $translate ? $this->translate($message) : $message;
1✔
478
        }
1✔
479

480

481
        /**
482
         * Returns errors corresponding to control.
483
         */
484
        public function getError(): ?string
485
        {
486
                return $this->errors ? implode(' ', array_unique($this->errors)) : null;
1✔
487
        }
488

489

490
        /**
491
         * Returns errors corresponding to control.
492
         */
493
        public function getErrors(): array
494
        {
495
                return array_unique($this->errors);
1✔
496
        }
497

498

499
        public function hasErrors(): bool
500
        {
501
                return (bool) $this->errors;
1✔
502
        }
503

504

505
        public function cleanErrors(): void
506
        {
507
                $this->errors = [];
1✔
508
        }
1✔
509

510

511
        /********************* user data ****************d*g**/
512

513

514
        /**
515
         * Sets user-specific option.
516
         */
517
        public function setOption($key, mixed $value): static
1✔
518
        {
519
                if ($value === null) {
1✔
UNCOV
520
                        unset($this->options[$key]);
×
521
                } else {
522
                        $this->options[$key] = $value;
1✔
523
                }
524

525
                return $this;
1✔
526
        }
527

528

529
        /**
530
         * Returns user-specific option.
531
         */
532
        public function getOption($key): mixed
533
        {
534
                if (func_num_args() > 1) {
1✔
UNCOV
535
                        trigger_error(__METHOD__ . '() parameter $default is deprecated, use operator ??', E_USER_DEPRECATED);
×
536
                        $default = func_get_arg(1);
×
537
                }
538
                return $this->options[$key] ?? $default ?? null;
1✔
539
        }
540

541

542
        /**
543
         * Returns user-specific options.
544
         */
545
        public function getOptions(): array
546
        {
UNCOV
547
                return $this->options;
×
548
        }
549

550

551
        /********************* extension methods ****************d*g**/
552

553

554
        public function __call(string $name, array $args): mixed
1✔
555
        {
556
                $class = static::class;
1✔
557
                do {
558
                        if (isset(self::$extMethods[$name][$class])) {
1✔
559
                                return (self::$extMethods[$name][$class])($this, ...$args);
1✔
560
                        }
561

562
                        $class = get_parent_class($class);
1✔
563
                } while ($class);
1✔
564

565
                return parent::__call($name, $args);
1✔
566
        }
567

568

569
        public static function extensionMethod(string $name, /*callable*/ $callback): void
1✔
570
        {
571
                if (str_contains($name, '::')) { // back compatibility
1✔
UNCOV
572
                        [, $name] = explode('::', $name);
×
573
                }
574

575
                self::$extMethods[$name][static::class] = $callback;
1✔
576
        }
1✔
577
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc