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

nette / application / 28120116131

24 Jun 2026 05:59PM UTC coverage: 84.766% (+0.2%) from 84.605%
28120116131

push

github

dg
added CLAUDE.md

1992 of 2350 relevant lines covered (84.77%)

0.85 hits per line

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

82.31
/src/Application/UI/Presenter.php
1
<?php declare(strict_types=1);
1✔
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
namespace Nette\Application\UI;
9

10
use Nette;
11
use Nette\Application;
12
use Nette\Application\Helpers;
13
use Nette\Application\LinkGenerator;
14
use Nette\Application\Responses;
15
use Nette\Http;
16
use Nette\Utils\Arrays;
17
use function array_slice, count, dirname, func_get_args, func_num_args, implode, in_array, is_array, is_dir, is_file, is_string, ltrim, preg_match, preg_replace, str_starts_with, strcasecmp, strlen, strncmp, strpos, strrpos, strtr, substr, substr_count, trigger_error, ucfirst;
18
use const DIRECTORY_SEPARATOR;
19

20

21
/**
22
 * Presenter component represents a webpage instance. It converts Request to Response.
23
 *
24
 * @property-deprecated Nette\Application\Request $request
25
 * @property-deprecated string $action
26
 * @property-deprecated string $view
27
 * @property-deprecated string|bool $layout
28
 * @property-read \stdClass $payload
29
 * @property-deprecated Nette\Http\Session $session
30
 * @property-read Nette\Security\User $user
31
 */
32
abstract class Presenter extends Control implements Application\IPresenter
33
{
34
        /** bad link handling {@link Presenter::$invalidLinkMode} */
35
        public const
36
                InvalidLinkSilent = 0b0000,
37
                InvalidLinkWarning = 0b0001,
38
                InvalidLinkException = 0b0010,
39
                InvalidLinkTextual = 0b0100;
40

41
        /** @internal special parameter key */
42
        public const
43
                PresenterKey = 'presenter',
44
                SignalKey = 'do',
45
                ActionKey = 'action',
46
                FlashKey = '_fid',
47
                DefaultAction = 'default';
48

49
        /** @deprecated use Presenter::InvalidLinkSilent */
50
        public const INVALID_LINK_SILENT = self::InvalidLinkSilent;
51

52
        /** @deprecated use Presenter::InvalidLinkWarning */
53
        public const INVALID_LINK_WARNING = self::InvalidLinkWarning;
54

55
        /** @deprecated use Presenter::InvalidLinkException */
56
        public const INVALID_LINK_EXCEPTION = self::InvalidLinkException;
57

58
        /** @deprecated use Presenter::InvalidLinkTextual */
59
        public const INVALID_LINK_TEXTUAL = self::InvalidLinkTextual;
60

61
        /** @deprecated use Presenter::PresenterKey */
62
        public const PRESENTER_KEY = self::PresenterKey;
63

64
        /** @deprecated use Presenter::SignalKey */
65
        public const SIGNAL_KEY = self::SignalKey;
66

67
        /** @deprecated use Presenter::ActionKey */
68
        public const ACTION_KEY = self::ActionKey;
69

70
        /** @deprecated use Presenter::FlashKey */
71
        public const FLASH_KEY = self::FlashKey;
72

73
        /** @deprecated use Presenter::DefaultAction */
74
        public const DEFAULT_ACTION = self::DefaultAction;
75

76
        public int $invalidLinkMode = self::InvalidLinkSilent;
77

78
        /** @var array<callable(static): void>  Occurs before starup() */
79
        public array $onStartup = [];
80

81
        /** @var array<callable(static): void>  Occurs before render*() and after beforeRender() */
82
        public array $onRender = [];
83

84
        /** @var array<callable(static, Application\Response): void>  Occurs before shutdown() */
85
        public array $onShutdown = [];
86

87
        /** automatically call canonicalize() */
88
        public bool $autoCanonicalize = true;
89

90
        /** use absolute Urls or paths? */
91
        public bool $absoluteUrls = false;
92

93
        /**
94
         * @var list<string>
95
         * @deprecated  use #[Requires(methods: ...)] to specify allowed methods
96
         */
97
        public array $allowedMethods = ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH'];
98
        private ?Nette\Application\Request $request = null;
99
        private ?Nette\Application\Response $response = null;
100

101
        /** @var array<string, array<string, mixed>> */
102
        private array $globalParams = [];
103

104
        /** @var array<string, mixed> */
105
        private array $globalState;
106

107
        /** @var ?array<string, string|false> */
108
        private ?array $globalStateSinces;
109
        private string $action = '';
110
        private string $view = '';
111
        private bool $forwarded = false;
112
        private string|bool $layout = '';
113
        private \stdClass $payload;
114
        private string $signalReceiver;
115
        private ?string $signal = null;
116
        private bool $ajaxMode;
117
        private bool $startupCheck = false;
118
        private readonly Nette\Http\IRequest $httpRequest;
119
        private readonly Nette\Http\IResponse $httpResponse;
120
        private readonly ?Nette\Http\Session $session;
121
        private readonly ?Nette\Security\User $user;
122
        private readonly ?TemplateFactory $templateFactory;
123
        private readonly LinkGenerator $linkGenerator;
124

125

126
        public function __construct()
127
        {
128
        }
1✔
129

130

131
        final public function getRequest(): Application\Request
132
        {
133
                return $this->request ?? throw new Nette\InvalidStateException('Request is not set, presenter is not running.');
1✔
134
        }
135

136

137
        /**
138
         * @return ($throw is true ? static : static)
139
         */
140
        final public function getPresenter(bool $throw = true): static
1✔
141
        {
142
                return $this;
1✔
143
        }
144

145

146
        /** @deprecated */
147
        final public function getPresenterIfExists(): static
148
        {
149
                return $this;
×
150
        }
151

152

153
        /** @deprecated */
154
        final public function hasPresenter(): bool
155
        {
156
                return true;
×
157
        }
158

159

160
        /**
161
         * Returns a name that uniquely identifies component.
162
         */
163
        public function getUniqueId(): string
164
        {
165
                return '';
1✔
166
        }
167

168

169
        /**
170
         * Checks whether the current presenter belongs to the given module.
171
         */
172
        public function isModuleCurrent(string $module): bool
1✔
173
        {
174
                $current = Helpers::splitName((string) $this->getName())[0];
1✔
175
                return str_starts_with($current . ':', ltrim($module . ':', ':'));
1✔
176
        }
177

178

179
        /**
180
         * Checks whether the current request was forwarded from another presenter or action.
181
         */
182
        public function isForwarded(): bool
183
        {
184
                return $this->forwarded || $this->request?->isMethod(Application\Request::FORWARD);
1✔
185
        }
186

187

188
        /********************* interface IPresenter ****************d*g**/
189

190

191
        public function run(Application\Request $request): Application\Response
1✔
192
        {
193
                $this->request = $request;
1✔
194
                $this->setParent($this->getParent(), $request->getPresenterName());
1✔
195

196
                if (!$this->httpResponse->isSent()) {
1✔
197
                        $this->httpResponse->addHeader('Vary', 'X-Requested-With');
1✔
198
                }
199

200
                $this->initGlobalParameters();
1✔
201

202
                try {
203
                        // CHECK REQUIREMENTS
204
                        (new AccessPolicy(static::getReflection()))->checkAccess($this);
1✔
205
                        $this->checkRequirements(static::getReflection());
1✔
206
                        $this->checkHttpMethod();
1✔
207

208
                        // STARTUP
209
                        Arrays::invoke($this->onStartup, $this);
1✔
210
                        $this->startup();
1✔
211
                        if (!$this->startupCheck) {
1✔
212
                                $class = static::getReflection()->getMethod('startup')->getDeclaringClass()->getName();
×
213
                                throw new Nette\InvalidStateException("Method $class::startup() or its parents doesn't call parent::startup().");
×
214
                        }
215

216
                        // calls $this->action<Action>()
217
                        try {
218
                                actionMethod:
219
                                $this->tryCall(static::formatActionMethod($this->action), $this->params);
1✔
220
                        } catch (Application\SwitchException $e) {
1✔
221
                                $this->changeAction($e->getMessage());
1✔
222
                                $this->autoCanonicalize = false;
1✔
223
                                goto actionMethod;
1✔
224
                        }
225

226
                        // autoload components
227
                        foreach ($this->globalParams as $id => $foo) {
1✔
228
                                $this->getComponent($id, throw: false);
1✔
229
                        }
230

231
                        if ($this->autoCanonicalize) {
1✔
232
                                $this->canonicalize();
1✔
233
                        }
234

235
                        if ($this->httpRequest->isMethod('head')) {
1✔
236
                                $this->terminate();
×
237
                        }
238

239
                        // SIGNAL HANDLING
240
                        // calls $this->handle<Signal>()
241
                        $this->processSignal();
1✔
242

243
                        // RENDERING VIEW
244
                        $this->beforeRender();
1✔
245
                        Arrays::invoke($this->onRender, $this);
1✔
246
                        // calls $this->render<View>()
247
                        try {
248
                                renderMethod:
249
                                $this->tryCall(static::formatRenderMethod($this->view), $this->params);
1✔
250
                        } catch (Application\SwitchException $e) {
1✔
251
                                $this->setView($e->getMessage());
×
252
                                goto renderMethod;
×
253
                        }
254
                        $this->afterRender();
1✔
255

256
                        // finish template rendering
257
                        $this->sendTemplate();
1✔
258

259
                } catch (Application\SwitchException $e) {
1✔
260
                        throw new \LogicException('Switch is only allowed inside action*() or render*() method.', 0, $e);
×
261
                } catch (Application\AbortException) {
1✔
262
                }
263

264
                // save component tree persistent state
265
                $this->saveGlobalState();
1✔
266

267
                if ($this->isAjax()) {
1✔
268
                        $this->getPayload()->state = $this->getGlobalState();
1✔
269
                        try {
270
                                if ($this->response instanceof Responses\TextResponse && $this->isControlInvalid()) {
1✔
271
                                        $this->snippetMode = true;
×
272
                                        $this->response->send($this->httpRequest, $this->httpResponse);
×
273
                                        $this->sendPayload();
1✔
274
                                }
275
                        } catch (Application\AbortException) {
×
276
                        }
277
                }
278

279
                if ($this->hasFlashSession()) {
1✔
280
                        $this->getFlashSession()->setExpiration('30 seconds');
1✔
281
                }
282

283
                if (!$this->response) {
1✔
284
                        $this->response = new Responses\VoidResponse;
1✔
285
                }
286

287
                Arrays::invoke($this->onShutdown, $this, $this->response);
1✔
288
                $this->shutdown($this->response);
1✔
289

290
                return $this->response;
1✔
291
        }
292

293

294
        /**
295
         * Called before action method. Override to run initialization common to all actions.
296
         * @return void
297
         */
298
        protected function startup()
299
        {
300
                $this->startupCheck = true;
1✔
301
        }
1✔
302

303

304
        /**
305
         * Called before the view is rendered. Override to set up common template variables.
306
         * @return void
307
         */
308
        protected function beforeRender()
309
        {
310
        }
1✔
311

312

313
        /**
314
         * Called after the view is rendered. Override for post-render processing.
315
         */
316
        protected function afterRender(): void
317
        {
318
        }
1✔
319

320

321
        /**
322
         * Called after the response is ready. Override for cleanup after request handling.
323
         */
324
        protected function shutdown(Application\Response $response): void
1✔
325
        {
326
        }
1✔
327

328

329
        /**
330
         * This method will be called when CSRF is detected.
331
         */
332
        public function detectedCsrf(): void
333
        {
334
                try {
335
                        $this->redirect('this');
1✔
336
                } catch (InvalidLinkException $e) {
1✔
337
                        $this->error($e->getMessage());
×
338
                }
339
        }
340

341

342
        /** @deprecated  use #[Requires(methods: ...)] to specify allowed methods */
343
        protected function checkHttpMethod(): void
344
        {
345
                if ($this->allowedMethods &&
1✔
346
                        !in_array($method = $this->httpRequest->getMethod(), $this->allowedMethods, strict: true)
1✔
347
                ) {
348
                        $this->httpResponse->setHeader('Allow', implode(',', $this->allowedMethods));
×
349
                        $this->error("Method $method is not allowed", Nette\Http\IResponse::S405_MethodNotAllowed);
×
350
                }
351
        }
1✔
352

353

354
        /********************* signal handling ****************d*g**/
355

356

357
        /**
358
         * @throws BadSignalException
359
         */
360
        public function processSignal(): void
361
        {
362
                if (!isset($this->signal)) {
1✔
363
                        return;
1✔
364
                }
365

366
                $component = $this->signalReceiver === ''
1✔
367
                        ? $this
1✔
368
                        : $this->getComponent($this->signalReceiver, throw: false);
1✔
369
                if ($component === null) {
1✔
370
                        throw new BadSignalException("The signal receiver component '$this->signalReceiver' is not found.");
1✔
371

372
                } elseif (!$component instanceof SignalReceiver) {
1✔
373
                        throw new BadSignalException("The signal receiver component '$this->signalReceiver' is not SignalReceiver implementor.");
×
374
                }
375

376
                $component->signalReceived($this->signal);
1✔
377
                $this->signal = null;
1✔
378
        }
1✔
379

380

381
        /**
382
         * Returns pair signal receiver and name.
383
         * @return ?array{string, string}
384
         */
385
        final public function getSignal(): ?array
386
        {
387
                return $this->signal === null ? null : [$this->signalReceiver, $this->signal];
1✔
388
        }
389

390

391
        /**
392
         * Checks if the signal receiver is the given one.
393
         */
394
        final public function isSignalReceiver(
395
                Nette\ComponentModel\Component|string $component,
396
                string|true|null $signal = null,
397
        ): bool
398
        {
399
                if ($component instanceof Nette\ComponentModel\Component) {
×
400
                        $component = $component === $this
×
401
                                ? ''
×
402
                                : $component->lookupPath(self::class);
×
403
                }
404

405
                if ($this->signal === null) {
×
406
                        return false;
×
407

408
                } elseif ($signal === true) {
×
409
                        return $component === ''
×
410
                                || strncmp($this->signalReceiver . '-', $component . '-', strlen($component) + 1) === 0;
×
411

412
                } elseif ($signal === null) {
×
413
                        return $this->signalReceiver === $component;
×
414
                }
415

416
                return $this->signalReceiver === $component && strcasecmp($signal, $this->signal) === 0;
×
417
        }
418

419

420
        /********************* rendering ****************d*g**/
421

422

423
        /**
424
         * Returns current action name.
425
         */
426
        final public function getAction(bool $fullyQualified = false): string
1✔
427
        {
428
                return $fullyQualified
1✔
429
                        ? ':' . $this->getName() . ':' . $this->action
×
430
                        : $this->action;
1✔
431
        }
432

433

434
        /**
435
         * Changes current action.
436
         */
437
        public function changeAction(string $action): void
1✔
438
        {
439
                $this->forwarded = true;
1✔
440
                $this->action = $this->view = $action;
1✔
441
        }
1✔
442

443

444
        /**
445
         * Switch from current action or render method to another.
446
         */
447
        public function switch(string $action): never
1✔
448
        {
449
                throw new Application\SwitchException($action);
1✔
450
        }
×
451

452

453
        /**
454
         * Returns current view.
455
         */
456
        final public function getView(): string
457
        {
458
                return $this->view;
1✔
459
        }
460

461

462
        /**
463
         * Changes current view. Any name is allowed.
464
         */
465
        public function setView(string $view): static
1✔
466
        {
467
                $this->forwarded = true;
1✔
468
                $this->view = $view;
1✔
469
                return $this;
1✔
470
        }
471

472

473
        /**
474
         * Returns current layout name.
475
         */
476
        final public function getLayout(): string|bool
477
        {
478
                return $this->layout;
×
479
        }
480

481

482
        /**
483
         * Changes or disables layout.
484
         */
485
        public function setLayout(string|bool $layout): static
1✔
486
        {
487
                $this->layout = $layout === false ? false : (string) $layout;
1✔
488
                return $this;
1✔
489
        }
490

491

492
        /**
493
         * @throws Nette\Application\AbortException
494
         * @return never
495
         */
496
        public function sendTemplate(?Template $template = null): void
1✔
497
        {
498
                $template ??= $this->getTemplate();
1✔
499
                $this->completeTemplate($template);
1✔
500
                $this->sendResponse(new Responses\TextResponse($template));
1✔
501
        }
502

503

504
        /**
505
         * Completes template parameters and file before rendering.
506
         */
507
        protected function completeTemplate(Template $template): void
1✔
508
        {
509
                foreach ($this->getReflection()->getTemplateVariables($this) as $name) {
1✔
510
                        $template->$name ??= $this->$name;
1✔
511
                }
512
                if ($template->getFile() === null) {
1✔
513
                        $template->setFile($this->findTemplateFile());
×
514
                }
515
        }
1✔
516

517

518
        /**
519
         * Finds template file name.
520
         */
521
        public function findTemplateFile(): string
522
        {
523
                $files = $this->formatTemplateFiles();
×
524
                foreach ($files as $file) {
×
525
                        if (is_file($file)) {
×
526
                                return $file;
×
527
                        }
528
                }
529

530
                $file = strtr($files[0], '/', DIRECTORY_SEPARATOR);
×
531
                $this->error("Page not found. Missing template '$file'.");
×
532
        }
533

534

535
        /**
536
         * Finds layout template file name.
537
         * @internal
538
         */
539
        public function findLayoutTemplateFile(): ?string
540
        {
541
                if ($this->layout === false) {
×
542
                        return null;
×
543
                }
544

545
                $files = $this->formatLayoutTemplateFiles();
×
546
                foreach ($files as $file) {
×
547
                        if (is_file($file)) {
×
548
                                return $file;
×
549
                        }
550
                }
551

552
                if ($this->layout) {
×
553
                        $file = strtr($files[0], '/', DIRECTORY_SEPARATOR);
×
554
                        throw new Nette\FileNotFoundException("Layout not found. Missing template '$file'.");
×
555
                }
556

557
                return null;
×
558
        }
559

560

561
        /**
562
         * Formats layout template file names.
563
         * @return non-empty-list<string>
564
         */
565
        public function formatLayoutTemplateFiles(): array
566
        {
567
                if (is_string($this->layout) && preg_match('#/|\\\#', $this->layout)) {
1✔
568
                        return [$this->layout];
1✔
569
                }
570

571
                $layout = $this->layout ?: 'layout';
1✔
572
                $dir = dirname((string) static::getReflection()->getFileName());
1✔
573
                $levels = substr_count((string) $this->getName(), ':');
1✔
574
                if (!is_dir("$dir/templates")) {
1✔
575
                        $dir = dirname($origDir = $dir);
1✔
576
                        if (!is_dir("$dir/templates")) {
1✔
577
                                $list = ["$origDir/@$layout.latte"];
1✔
578
                                do {
579
                                        $list[] = "$dir/@$layout.latte";
1✔
580
                                } while ($levels-- && ($dir = dirname($dir)));
1✔
581
                                return $list;
1✔
582
                        }
583
                }
584

585
                [, $presenter] = Helpers::splitName((string) $this->getName());
1✔
586
                $list = [
1✔
587
                        "$dir/templates/$presenter/@$layout.latte",
1✔
588
                        "$dir/templates/$presenter.@$layout.latte",
1✔
589
                ];
590
                do {
591
                        $list[] = "$dir/templates/@$layout.latte";
1✔
592
                } while ($levels-- && ($dir = dirname($dir)));
1✔
593

594
                return $list;
1✔
595
        }
596

597

598
        /**
599
         * Formats view template file names.
600
         * @return non-empty-list<string>
601
         */
602
        public function formatTemplateFiles(): array
603
        {
604
                $dir = dirname((string) static::getReflection()->getFileName());
1✔
605
                if (!is_dir("$dir/templates")) {
1✔
606
                        $dir = dirname($origDir = $dir);
1✔
607
                        if (!is_dir("$dir/templates")) {
1✔
608
                                return [
609
                                        "$origDir/$this->view.latte",
1✔
610
                                ];
611
                        }
612
                }
613

614
                [, $presenter] = Helpers::splitName((string) $this->getName());
1✔
615
                return [
616
                        "$dir/templates/$presenter/$this->view.latte",
1✔
617
                        "$dir/templates/$presenter.$this->view.latte",
1✔
618
                ];
619
        }
620

621

622
        /**
623
         * Formats action method name.
624
         */
625
        public static function formatActionMethod(string $action): string
1✔
626
        {
627
                return 'action' . ucfirst($action);
1✔
628
        }
629

630

631
        /**
632
         * Formats render view method name.
633
         */
634
        public static function formatRenderMethod(string $view): string
1✔
635
        {
636
                return 'render' . ucfirst($view);
1✔
637
        }
638

639

640
        /**
641
         * @template T of Template
642
         * @param ?class-string<T>  $class
643
         * @return ($class is null ? Template : T)
644
         */
645
        protected function createTemplate(?string $class = null): Template
1✔
646
        {
647
                $class ??= $this->formatTemplateClass();
1✔
648
                return $this->getTemplateFactory()->createTemplate($this, $class);
1✔
649
        }
650

651

652
        /** @return ?class-string<Template> */
653
        public function formatTemplateClass(): ?string
654
        {
655
                $base = preg_replace('#Presenter$#', '', static::class);
1✔
656
                return $this->checkTemplateClass($base . ucfirst($this->action) . 'Template')
1✔
657
                        ?? $this->checkTemplateClass($base . 'Template');
1✔
658
        }
659

660

661
        /********************* partial AJAX rendering ****************d*g**/
662

663

664
        final public function getPayload(): \stdClass
665
        {
666
                return $this->payload ??= new \stdClass;
1✔
667
        }
668

669

670
        /**
671
         * Is AJAX request?
672
         */
673
        public function isAjax(): bool
674
        {
675
                if (!isset($this->ajaxMode)) {
1✔
676
                        $this->ajaxMode = $this->httpRequest->isAjax();
1✔
677
                }
678

679
                return $this->ajaxMode;
1✔
680
        }
681

682

683
        /**
684
         * Sends AJAX payload to the output.
685
         * @throws Nette\Application\AbortException
686
         * @return never
687
         */
688
        public function sendPayload(): void
689
        {
690
                $this->sendResponse(new Responses\JsonResponse($this->getPayload()));
×
691
        }
692

693

694
        /**
695
         * Sends JSON data to the output.
696
         * @throws Nette\Application\AbortException
697
         * @return never
698
         */
699
        public function sendJson(mixed $data): void
700
        {
701
                $this->sendResponse(new Responses\JsonResponse($data));
×
702
        }
703

704

705
        /********************* navigation & flow ****************d*g**/
706

707

708
        /**
709
         * Sends response and terminates presenter.
710
         * @throws Nette\Application\AbortException
711
         * @return never
712
         */
713
        public function sendResponse(Application\Response $response): void
1✔
714
        {
715
                $this->response = $response;
1✔
716
                $this->terminate();
1✔
717
        }
718

719

720
        /**
721
         * Correctly terminates presenter.
722
         * @throws Nette\Application\AbortException
723
         * @return never
724
         */
725
        public function terminate(): void
726
        {
727
                throw new Application\AbortException;
1✔
728
        }
729

730

731
        /**
732
         * Forward to another presenter or action.
733
         * @param  array|mixed  $args
734
         * @throws Nette\Application\AbortException
735
         * @return never
736
         */
737
        public function forward(string|Nette\Application\Request $destination, $args = []): void
1✔
738
        {
739
                if ($destination instanceof Application\Request) {
1✔
740
                        $this->sendResponse(new Responses\ForwardResponse($destination));
×
741
                }
742

743
                $args = func_num_args() < 3 && is_array($args)
1✔
744
                        ? $args
1✔
745
                        : array_slice(func_get_args(), 1);
×
746
                $request = $this->linkGenerator->createRequest($this, $destination, $args, 'forward');
1✔
747
                $this->sendResponse(new Responses\ForwardResponse($request));
1✔
748
        }
749

750

751
        /**
752
         * Redirect to another URL and ends presenter execution.
753
         * @throws Nette\Application\AbortException
754
         * @return never
755
         */
756
        public function redirectUrl(string $url, ?int $httpCode = null): void
1✔
757
        {
758
                if ($this->isAjax()) {
1✔
759
                        $this->getPayload()->redirect = $url;
×
760
                        $this->sendPayload();
×
761

762
                } elseif (!$httpCode) {
1✔
763
                        $httpCode = $this->httpRequest->isMethod('post')
1✔
764
                                ? Http\IResponse::S303_PostGet
1✔
765
                                : Http\IResponse::S302_Found;
1✔
766
                }
767

768
                $this->sendResponse(new Responses\RedirectResponse($url, $httpCode));
1✔
769
        }
770

771

772
        /**
773
         * Returns the last created Request.
774
         * @internal
775
         */
776
        final public function getLastCreatedRequest(): ?Application\Request
777
        {
778
                return $this->linkGenerator->lastRequest;
1✔
779
        }
780

781

782
        /**
783
         * Returns the last created Request flag.
784
         * @internal
785
         */
786
        final public function getLastCreatedRequestFlag(string $flag): bool
1✔
787
        {
788
                return (bool) $this->linkGenerator->lastRequest?->hasFlag($flag);
1✔
789
        }
790

791

792
        /**
793
         * Conditional redirect to canonicalized URI.
794
         * @param  mixed  ...$args
795
         * @throws Nette\Application\AbortException
796
         */
797
        public function canonicalize(?string $destination = null, ...$args): void
1✔
798
        {
799
                $request = $this->getRequest();
1✔
800
                if ($this->isAjax() || (!$request->isMethod('get') && !$request->isMethod('head'))) {
1✔
801
                        return;
1✔
802
                }
803

804
                $args = count($args) === 1 && is_array($args[0] ?? null)
1✔
805
                        ? $args[0]
×
806
                        : $args;
1✔
807
                try {
808
                        $url = $this->linkGenerator->link(
1✔
809
                                $destination ?: $this->action,
1✔
810
                                $args + $this->getGlobalState() + $request->getParameters(),
1✔
811
                                $this,
812
                                'redirectX',
1✔
813
                        );
814
                } catch (InvalidLinkException) {
×
815
                }
816

817
                if (!isset($url) || $this->httpRequest->getUrl()->isEqual($url)) {
1✔
818
                        return;
×
819
                }
820

821
                $code = $request->hasFlag($request::VARYING)
1✔
822
                        ? Http\IResponse::S302_Found
1✔
823
                        : Http\IResponse::S301_MovedPermanently;
1✔
824
                $this->sendResponse(new Responses\RedirectResponse($url, $code));
1✔
825
        }
826

827

828
        /**
829
         * Attempts to cache the sent entity by its last modification date.
830
         * @param  ?string  $etag  strong entity tag validator
831
         * @param  ?string  $expire  like '20 minutes'
832
         * @throws Nette\Application\AbortException
833
         */
834
        public function lastModified(
835
                string|int|\DateTimeInterface|null $lastModified,
836
                ?string $etag = null,
837
                ?string $expire = null,
838
        ): void
839
        {
840
                if ($expire !== null) {
×
841
                        $this->httpResponse->setExpiration($expire);
×
842
                }
843

844
                $helper = new Http\Context($this->httpRequest, $this->httpResponse);
×
845
                if (!$helper->isModified($lastModified, $etag)) {
×
846
                        $this->terminate();
×
847
                }
848
        }
849

850

851
        /**
852
         * @param  array<string, mixed>  $args
853
         */
854
        #[\Deprecated]
855
        protected function createRequest(Component $component, string $destination, array $args, string $mode): ?string
856
        {
857
                return $this->linkGenerator->link($destination, $args, $component, $mode);
×
858
        }
859

860

861
        /**
862
         * @return array{absolute: bool, path: string, signal: bool, args: ?array<string, mixed>, fragment: string}
863
         */
864
        #[\Deprecated]
865
        public static function parseDestination(string $destination): array
866
        {
867
                return LinkGenerator::parseDestination($destination);
×
868
        }
869

870

871
        #[\Deprecated]
872
        protected function requestToUrl(Application\Request $request, ?bool $relative = null): string
873
        {
874
                return $this->linkGenerator->requestToUrl($request, $relative ?? !$this->absoluteUrls);
×
875
        }
876

877

878
        /**
879
         * Invalid link handler. Descendant can override this method to change default behaviour.
880
         * @throws InvalidLinkException
881
         */
882
        protected function handleInvalidLink(InvalidLinkException $e): string
1✔
883
        {
884
                if ($this->invalidLinkMode & self::InvalidLinkException) {
1✔
885
                        throw $e;
1✔
886
                } elseif ($this->invalidLinkMode & self::InvalidLinkWarning) {
1✔
887
                        trigger_error('Invalid link: ' . $e->getMessage(), E_USER_WARNING);
1✔
888
                }
889

890
                return $this->invalidLinkMode & self::InvalidLinkTextual
1✔
891
                        ? '#error: ' . $e->getMessage()
1✔
892
                        : '#';
1✔
893
        }
894

895

896
        /********************* request serialization ****************d*g**/
897

898

899
        /**
900
         * Stores current request to session.
901
         */
902
        public function storeRequest(string $expiration = '+ 10 minutes'): string
1✔
903
        {
904
                $session = $this->getSession('Nette.Application/requests');
1✔
905
                do {
906
                        $key = Nette\Utils\Random::generate(5);
1✔
907
                } while ($session->get($key));
1✔
908

909
                $session->set($key, [$this->user?->getId(), $this->request]);
1✔
910
                $session->setExpiration($expiration, $key);
1✔
911
                return $key;
1✔
912
        }
913

914

915
        /**
916
         * Restores request from session.
917
         */
918
        public function restoreRequest(string $key): void
1✔
919
        {
920
                $session = $this->getSession('Nette.Application/requests');
1✔
921
                $data = $session->get($key);
1✔
922
                if (!$data || ($data[0] !== null && $data[0] !== $this->getUser()->getId())) {
1✔
923
                        return;
1✔
924
                }
925

926
                $request = clone $data[1];
1✔
927
                assert($request instanceof Application\Request);
928
                $session->remove($key);
1✔
929
                $params = $request->getParameters();
1✔
930
                $params[self::FlashKey] = $this->getFlashKey();
1✔
931
                $request->setParameters($params);
1✔
932
                if ($request->isMethod('POST')) {
1✔
933
                        $request->setFlag(Application\Request::RESTORED, true);
1✔
934
                        $this->sendResponse(new Responses\ForwardResponse($request));
1✔
935
                } else {
936
                        $this->redirectUrl($this->linkGenerator->requestToUrl($request));
×
937
                }
938
        }
939

940

941
        /********************* interface StatePersistent ****************d*g**/
942

943

944
        /**
945
         * Descendant can override this method to return the names of custom persistent components.
946
         * @return list<string>
947
         */
948
        public static function getPersistentComponents(): array
949
        {
950
                return [];
1✔
951
        }
952

953

954
        /**
955
         * Saves state information for all subcomponents to $this->globalState.
956
         * @param  ?class-string  $forClass
957
         * @return array<string, mixed>
958
         */
959
        public function getGlobalState(?string $forClass = null): array
1✔
960
        {
961
                $sinces = &$this->globalStateSinces;
1✔
962

963
                if (!isset($this->globalState)) {
1✔
964
                        $state = [];
1✔
965
                        foreach ($this->globalParams as $id => $params) {
1✔
966
                                $prefix = $id . self::NameSeparator;
1✔
967
                                foreach ($params as $key => $val) {
1✔
968
                                        $state[$prefix . $key] = $val;
1✔
969
                                }
970
                        }
971

972
                        $this->saveStatePartial($state, new ComponentReflection($forClass ?? $this));
1✔
973

974
                        if ($sinces === null) {
1✔
975
                                $sinces = [];
1✔
976
                                foreach ($this->getReflection()->getPersistentParams() as $name => $meta) {
1✔
977
                                        $sinces[$name] = $meta['since'];
1✔
978
                                }
979
                        }
980

981
                        $persistents = $this->getReflection()->getPersistentComponents();
1✔
982

983
                        $since = false;
1✔
984
                        foreach ($this->getComponentTree() as $component) {
1✔
985
                                if ($component->getParent() === $this) {
1✔
986
                                        // counts on child-first search
987
                                        $since = $persistents[(string) $component->getName()]['since'] ?? false; // false = nonpersistent
1✔
988
                                }
989

990
                                if (!$component instanceof StatePersistent || !$component instanceof Component) {
1✔
991
                                        continue;
×
992
                                }
993

994
                                $prefix = $component->getUniqueId() . self::NameSeparator;
1✔
995
                                $params = [];
1✔
996
                                $component->saveState($params);
1✔
997
                                foreach ($params as $key => $val) {
1✔
998
                                        $state[$prefix . $key] = $val;
1✔
999
                                        $sinces[$prefix . $key] = $since;
1✔
1000
                                }
1001
                        }
1002
                } else {
1003
                        $state = $this->globalState;
1✔
1004
                }
1005

1006
                if ($forClass !== null) {
1✔
1007
                        $tree = Helpers::getClassesAndTraits($forClass);
1✔
1008
                        $since = null;
1✔
1009
                        foreach ($state as $key => $foo) {
1✔
1010
                                if (!isset($sinces[$key])) {
1✔
1011
                                        $x = strpos($key, self::NameSeparator);
1✔
1012
                                        $x = $x === false ? $key : substr($key, 0, $x);
1✔
1013
                                        $sinces[$key] = $sinces[$x] ?? false;
1✔
1014
                                }
1015

1016
                                if ($since !== $sinces[$key]) {
1✔
1017
                                        $since = $sinces[$key];
1✔
1018
                                        $ok = $since && isset($tree[$since]);
1✔
1019
                                }
1020

1021
                                if (!$ok) {
1✔
1022
                                        unset($state[$key]);
1✔
1023
                                }
1024
                        }
1025
                }
1026

1027
                return $state;
1✔
1028
        }
1029

1030

1031
        /**
1032
         * Permanently saves state information for all subcomponents to $this->globalState.
1033
         */
1034
        protected function saveGlobalState(): void
1035
        {
1036
                $this->globalParams = [];
1✔
1037
                $this->globalState = $this->getGlobalState();
1✔
1038
        }
1✔
1039

1040

1041
        /**
1042
         * Initializes $this->globalParams, $this->signal & $this->signalReceiver, $this->action, $this->view. Called by run().
1043
         * @throws Nette\Application\BadRequestException if action name is not valid
1044
         */
1045
        private function initGlobalParameters(): void
1046
        {
1047
                // init $this->globalParams
1048
                $this->globalParams = [];
1✔
1049
                $selfParams = [];
1✔
1050
                $request = $this->getRequest();
1✔
1051

1052
                $params = $request->getParameters();
1✔
1053
                if (($tmp = $request->getPost('_' . self::SignalKey)) !== null) {
1✔
1054
                        $params[self::SignalKey] = $tmp;
1✔
1055
                } elseif ($this->isAjax()) {
1✔
1056
                        $params += $request->getPost();
1✔
1057
                        if (($tmp = $request->getPost(self::SignalKey)) !== null) {
1✔
1058
                                $params[self::SignalKey] = $tmp;
1✔
1059
                        }
1060
                }
1061

1062
                foreach ($params as $key => $value) {
1✔
1063
                        if (!preg_match('#^((?:[a-z0-9_]+-)*)((?!\d+$)[a-z0-9_]+)$#Di', (string) $key, $matches)) {
1✔
1064
                                continue;
×
1065
                        } elseif (!$matches[1]) {
1✔
1066
                                $selfParams[$key] = $value;
1✔
1067
                        } else {
1068
                                $this->globalParams[substr($matches[1], 0, -1)][$matches[2]] = $value;
1✔
1069
                        }
1070
                }
1071

1072
                // init & validate $this->action & $this->view
1073
                $action = $selfParams[self::ActionKey] ?? self::DefaultAction;
1✔
1074
                if (!is_string($action) || !Nette\Utils\Strings::match($action, '#^[a-zA-Z0-9][a-zA-Z0-9_\x7f-\xff]*$#D')) {
1✔
1075
                        $this->error('Action name is not valid.');
1✔
1076
                }
1077

1078
                $this->changeAction($action);
1✔
1079
                $this->forwarded = false;
1✔
1080

1081
                // init $this->signalReceiver and key 'signal' in appropriate params array
1082
                $this->signalReceiver = $this->getUniqueId();
1✔
1083
                if (isset($selfParams[self::SignalKey])) {
1✔
1084
                        $param = $selfParams[self::SignalKey];
1✔
1085
                        if (!is_string($param)) {
1✔
1086
                                $this->error('Signal name is not string.');
1✔
1087
                        }
1088

1089
                        $pos = strrpos($param, '-');
1✔
1090
                        if ($pos) {
1✔
1091
                                $this->signalReceiver = substr($param, 0, $pos);
1✔
1092
                                $this->signal = substr($param, $pos + 1);
1✔
1093
                        } else {
1094
                                $this->signalReceiver = $this->getUniqueId();
1✔
1095
                                $this->signal = $param;
1✔
1096
                        }
1097

1098
                        if ($this->signal === '') {
1✔
1099
                                $this->signal = null;
×
1100
                        }
1101
                }
1102

1103
                $this->loadState($selfParams);
1✔
1104
        }
1✔
1105

1106

1107
        /**
1108
         * Pops parameters for specified component.
1109
         * @return array<string, mixed>
1110
         * @internal
1111
         */
1112
        final public function popGlobalParameters(string $id): array
1✔
1113
        {
1114
                $res = $this->globalParams[$id] ?? [];
1✔
1115
                unset($this->globalParams[$id]);
1✔
1116
                return $res;
1✔
1117
        }
1118

1119

1120
        /********************* flash session ****************d*g**/
1121

1122

1123
        private function getFlashKey(): ?string
1124
        {
1125
                $flashKey = $this->getParameter(self::FlashKey);
1✔
1126
                return is_string($flashKey) && $flashKey !== ''
1✔
1127
                        ? $flashKey
1✔
1128
                        : null;
1✔
1129
        }
1130

1131

1132
        /**
1133
         * Checks if a flash session namespace exists.
1134
         */
1135
        public function hasFlashSession(): bool
1136
        {
1137
                $flashKey = $this->getFlashKey();
1✔
1138
                return $flashKey !== null
1✔
1139
                        && $this->getSession()->hasSection('Nette.Application.Flash/' . $flashKey);
1✔
1140
        }
1141

1142

1143
        /**
1144
         * Returns session namespace provided to pass temporary data between redirects.
1145
         */
1146
        public function getFlashSession(): Http\SessionSection
1147
        {
1148
                $flashKey = $this->getFlashKey();
1✔
1149
                if ($flashKey === null) {
1✔
1150
                        $this->params[self::FlashKey] = $flashKey = Nette\Utils\Random::generate(4);
1✔
1151
                }
1152

1153
                return $this->getSession('Nette.Application.Flash/' . $flashKey);
1✔
1154
        }
1155

1156

1157
        /********************* services ****************d*g**/
1158

1159

1160
        final public function injectPrimary(
1✔
1161
                Http\IRequest $httpRequest,
1162
                Http\IResponse $httpResponse,
1163
                ?Application\IPresenterFactory $presenterFactory = null,
1164
                ?Nette\Routing\Router $router = null,
1165
                ?Http\Session $session = null,
1166
                ?Nette\Security\User $user = null,
1167
                ?TemplateFactory $templateFactory = null,
1168
        ): void
1169
        {
1170
                $this->httpRequest = $httpRequest;
1✔
1171
                $this->httpResponse = $httpResponse;
1✔
1172
                $this->session = $session;
1✔
1173
                $this->user = $user;
1✔
1174
                $this->templateFactory = $templateFactory;
1✔
1175
                if ($router && $presenterFactory) {
1✔
1176
                        $url = $httpRequest->getUrl();
1✔
1177
                        $this->linkGenerator = new LinkGenerator(
1✔
1178
                                $router,
1✔
1179
                                new Http\UrlScript($url->getHostUrl() . $url->getScriptPath()),
1✔
1180
                                $presenterFactory,
1181
                        );
1182
                }
1183
        }
1✔
1184

1185

1186
        final public function getHttpRequest(): Http\IRequest
1187
        {
1188
                return $this->httpRequest;
1✔
1189
        }
1190

1191

1192
        final public function getHttpResponse(): Http\IResponse
1193
        {
1194
                return $this->httpResponse;
1✔
1195
        }
1196

1197

1198
        /**
1199
         * @return ($namespace is null ? Http\Session : Http\SessionSection)
1200
         */
1201
        final public function getSession(?string $namespace = null): Http\Session|Http\SessionSection
1✔
1202
        {
1203
                if (empty($this->session)) {
1✔
1204
                        throw new Nette\InvalidStateException('Service Session has not been set.');
×
1205
                }
1206

1207
                return $namespace === null
1✔
1208
                        ? $this->session
1✔
1209
                        : $this->session->getSection($namespace);
1✔
1210
        }
1211

1212

1213
        final public function getUser(): Nette\Security\User
1214
        {
1215
                return $this->user ?? throw new Nette\InvalidStateException('Service User has not been set.');
1✔
1216
        }
1217

1218

1219
        final public function getTemplateFactory(): TemplateFactory
1220
        {
1221
                return $this->templateFactory ?? throw new Nette\InvalidStateException('Service TemplateFactory has not been set.');
1✔
1222
        }
1223

1224

1225
        final protected function getLinkGenerator(): LinkGenerator
1226
        {
1227
                return $this->linkGenerator ?? throw new Nette\InvalidStateException('Unable to create link to other presenter, service PresenterFactory or Router has not been set.');
1✔
1228
        }
1229
}
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

© 2026 Coveralls, Inc