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

nette / application / 28331702164

28 Jun 2026 06:23PM UTC coverage: 84.573% (-0.2%) from 84.766%
28331702164

push

github

dg
added CLAUDE.md

2001 of 2366 relevant lines covered (84.57%)

0.85 hits per line

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

81.89
/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((string) $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
                try {
747
                        $request = $this->linkGenerator->createRequest($this, $destination, $args, 'forward');
1✔
748
                } catch (InvalidRequestParameterException $e) {
×
749
                        $this->error($e->getMessage());
×
750
                }
751

752
                $this->sendResponse(new Responses\ForwardResponse($request));
1✔
753
        }
754

755

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

767
                } elseif (!$httpCode) {
1✔
768
                        $httpCode = $this->httpRequest->isMethod('post')
1✔
769
                                ? Http\IResponse::S303_PostGet
1✔
770
                                : Http\IResponse::S302_Found;
1✔
771
                }
772

773
                $this->sendResponse(new Responses\RedirectResponse($url, $httpCode));
1✔
774
        }
775

776

777
        /**
778
         * Returns the last created Request.
779
         * @internal
780
         */
781
        final public function getLastCreatedRequest(): ?Application\Request
782
        {
783
                return $this->linkGenerator->lastRequest;
1✔
784
        }
785

786

787
        /**
788
         * Returns the last created Request flag.
789
         * @internal
790
         */
791
        final public function getLastCreatedRequestFlag(string $flag): bool
1✔
792
        {
793
                return (bool) $this->linkGenerator->lastRequest?->hasFlag($flag);
1✔
794
        }
795

796

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

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

822
                if (!isset($url) || $this->httpRequest->getUrl()->isEqual($url)) {
1✔
823
                        return;
×
824
                }
825

826
                $code = $request->hasFlag($request::VARYING)
1✔
827
                        ? Http\IResponse::S302_Found
1✔
828
                        : Http\IResponse::S301_MovedPermanently;
1✔
829
                $this->sendResponse(new Responses\RedirectResponse($url, $code));
1✔
830
        }
831

832

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

849
                $helper = new Http\Context($this->httpRequest, $this->httpResponse);
×
850
                if (!$helper->isModified($lastModified, $etag)) {
×
851
                        $this->terminate();
×
852
                }
853
        }
854

855

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

865

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

875

876
        #[\Deprecated]
877
        protected function requestToUrl(Application\Request $request, ?bool $relative = null): string
878
        {
879
                return $this->linkGenerator->requestToUrl($request, $relative ?? !$this->absoluteUrls);
×
880
        }
881

882

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

895
                return $this->invalidLinkMode & self::InvalidLinkTextual
1✔
896
                        ? '#error: ' . $e->getMessage()
1✔
897
                        : '#';
1✔
898
        }
899

900

901
        /********************* request serialization ****************d*g**/
902

903

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

914
                $session->set($key, [$this->user?->getId(), $this->request]);
1✔
915
                $session->setExpiration($expiration, $key);
1✔
916
                return $key;
1✔
917
        }
918

919

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

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

945

946
        /********************* interface StatePersistent ****************d*g**/
947

948

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

958

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

968
                if (!isset($this->globalState)) {
1✔
969
                        $state = [];
1✔
970
                        foreach ($this->globalParams as $id => $params) {
1✔
971
                                $prefix = $id . self::NameSeparator;
1✔
972
                                foreach ($params as $key => $val) {
1✔
973
                                        $state[$prefix . $key] = $val;
1✔
974
                                }
975
                        }
976

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

979
                        if ($sinces === null) {
1✔
980
                                $sinces = [];
1✔
981
                                foreach ($this->getReflection()->getPersistentParams() as $name => $meta) {
1✔
982
                                        $sinces[$name] = $meta['since'];
1✔
983
                                }
984
                        }
985

986
                        $persistents = $this->getReflection()->getPersistentComponents();
1✔
987

988
                        $since = false;
1✔
989
                        foreach ($this->getComponentTree() as $component) {
1✔
990
                                if ($component->getParent() === $this) {
1✔
991
                                        // counts on child-first search
992
                                        $since = $persistents[(string) $component->getName()]['since'] ?? false; // false = nonpersistent
1✔
993
                                }
994

995
                                if (!$component instanceof StatePersistent || !$component instanceof Component) {
1✔
996
                                        continue;
×
997
                                }
998

999
                                $prefix = $component->getUniqueId() . self::NameSeparator;
1✔
1000
                                $params = [];
1✔
1001
                                $component->saveState($params);
1✔
1002
                                foreach ($params as $key => $val) {
1✔
1003
                                        $state[$prefix . $key] = $val;
1✔
1004
                                        $sinces[$prefix . $key] = $since;
1✔
1005
                                }
1006
                        }
1007
                } else {
1008
                        $state = $this->globalState;
1✔
1009
                }
1010

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

1021
                                if ($since !== $sinces[$key]) {
1✔
1022
                                        $since = $sinces[$key];
1✔
1023
                                        $ok = $since && isset($tree[$since]);
1✔
1024
                                }
1025

1026
                                if (!$ok) {
1✔
1027
                                        unset($state[$key]);
1✔
1028
                                }
1029
                        }
1030
                }
1031

1032
                return $state;
1✔
1033
        }
1034

1035

1036
        /**
1037
         * Permanently saves state information for all subcomponents to $this->globalState.
1038
         */
1039
        protected function saveGlobalState(): void
1040
        {
1041
                $this->globalParams = [];
1✔
1042
                $this->globalState = $this->getGlobalState();
1✔
1043
        }
1✔
1044

1045

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

1057
                $params = $request->getParameters();
1✔
1058
                if (($tmp = $request->getPost('_' . self::SignalKey)) !== null) {
1✔
1059
                        $params[self::SignalKey] = $tmp;
1✔
1060
                } elseif ($this->isAjax()) {
1✔
1061
                        $params += $request->getPost();
1✔
1062
                        if (($tmp = $request->getPost(self::SignalKey)) !== null) {
1✔
1063
                                $params[self::SignalKey] = $tmp;
1✔
1064
                        }
1065
                }
1066

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

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

1083
                $this->changeAction($action);
1✔
1084
                $this->forwarded = false;
1✔
1085

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

1094
                        $pos = strrpos($param, '-');
1✔
1095
                        if ($pos) {
1✔
1096
                                $this->signalReceiver = substr($param, 0, $pos);
1✔
1097
                                $this->signal = substr($param, $pos + 1);
1✔
1098
                        } else {
1099
                                $this->signalReceiver = $this->getUniqueId();
1✔
1100
                                $this->signal = $param;
1✔
1101
                        }
1102

1103
                        if ($this->signal === '') {
1✔
1104
                                $this->signal = null;
×
1105
                        }
1106
                }
1107

1108
                $this->loadState($selfParams);
1✔
1109
        }
1✔
1110

1111

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

1124

1125
        /********************* flash session ****************d*g**/
1126

1127

1128
        private function getFlashKey(): ?string
1129
        {
1130
                $flashKey = $this->getParameter(self::FlashKey);
1✔
1131
                return is_string($flashKey) && $flashKey !== ''
1✔
1132
                        ? $flashKey
1✔
1133
                        : null;
1✔
1134
        }
1135

1136

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

1147

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

1158
                return $this->getSession('Nette.Application.Flash/' . $flashKey);
1✔
1159
        }
1160

1161

1162
        /********************* services ****************d*g**/
1163

1164

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

1190

1191
        final public function getHttpRequest(): Http\IRequest
1192
        {
1193
                return $this->httpRequest;
1✔
1194
        }
1195

1196

1197
        final public function getHttpResponse(): Http\IResponse
1198
        {
1199
                return $this->httpResponse;
1✔
1200
        }
1201

1202

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

1212
                return $namespace === null
1✔
1213
                        ? $this->session
1✔
1214
                        : $this->session->getSection($namespace);
1✔
1215
        }
1216

1217

1218
        final public function getUser(): Nette\Security\User
1219
        {
1220
                return $this->user ?? throw new Nette\InvalidStateException('Service User has not been set.');
1✔
1221
        }
1222

1223

1224
        final public function getTemplateFactory(): TemplateFactory
1225
        {
1226
                return $this->templateFactory ?? throw new Nette\InvalidStateException('Service TemplateFactory has not been set.');
1✔
1227
        }
1228

1229

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