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

nette / application / 20368461488

19 Dec 2025 11:19AM UTC coverage: 82.434% (+0.7%) from 81.757%
20368461488

push

github

dg
improved phpDoc (#362)

1971 of 2391 relevant lines covered (82.43%)

0.82 hits per line

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

75.59
/src/Application/UI/Presenter.php
1
<?php
2

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

8
declare(strict_types=1);
9

10
namespace Nette\Application\UI;
11

12
use Nette;
13
use Nette\Application;
14
use Nette\Application\Helpers;
15
use Nette\Application\LinkGenerator;
16
use Nette\Application\Responses;
17
use Nette\Http;
18
use Nette\Utils\Arrays;
19
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;
20
use const DIRECTORY_SEPARATOR;
21

22

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

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

51
        /** @deprecated use Presenter::InvalidLinkSilent */
52
        public const INVALID_LINK_SILENT = self::InvalidLinkSilent;
53

54
        /** @deprecated use Presenter::InvalidLinkWarning */
55
        public const INVALID_LINK_WARNING = self::InvalidLinkWarning;
56

57
        /** @deprecated use Presenter::InvalidLinkException */
58
        public const INVALID_LINK_EXCEPTION = self::InvalidLinkException;
59

60
        /** @deprecated use Presenter::InvalidLinkTextual */
61
        public const INVALID_LINK_TEXTUAL = self::InvalidLinkTextual;
62

63
        /** @deprecated use Presenter::PresenterKey */
64
        public const PRESENTER_KEY = self::PresenterKey;
65

66
        /** @deprecated use Presenter::SignalKey */
67
        public const SIGNAL_KEY = self::SignalKey;
68

69
        /** @deprecated use Presenter::ActionKey */
70
        public const ACTION_KEY = self::ActionKey;
71

72
        /** @deprecated use Presenter::FlashKey */
73
        public const FLASH_KEY = self::FlashKey;
74

75
        /** @deprecated use Presenter::DefaultAction */
76
        public const DEFAULT_ACTION = self::DefaultAction;
77

78
        public int $invalidLinkMode = self::InvalidLinkSilent;
79

80
        /** @var array<callable(self): void>  Occurs before starup() */
81
        public array $onStartup = [];
82

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

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

89
        /** automatically call canonicalize() */
90
        public bool $autoCanonicalize = true;
91

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

95
        /** @deprecated  use #[Requires(methods: ...)] to specify allowed methods */
96
        public array $allowedMethods = ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH'];
97
        private ?Nette\Application\Request $request = null;
98
        private ?Nette\Application\Response $response = null;
99
        private array $globalParams = [];
100
        private array $globalState;
101
        private ?array $globalStateSinces;
102
        private string $action = '';
103
        private string $view = '';
104
        private bool $forwarded = false;
105
        private string|bool $layout = '';
106
        private \stdClass $payload;
107
        private string $signalReceiver;
108
        private ?string $signal = null;
109
        private bool $ajaxMode;
110
        private bool $startupCheck = false;
111
        private readonly Nette\Http\IRequest $httpRequest;
112
        private readonly Nette\Http\IResponse $httpResponse;
113
        private readonly ?Nette\Http\Session $session;
114
        private readonly ?Nette\Security\User $user;
115
        private readonly ?TemplateFactory $templateFactory;
116
        private readonly LinkGenerator $linkGenerator;
117

118

119
        public function __construct()
120
        {
121
        }
1✔
122

123

124
        final public function getRequest(): ?Application\Request
125
        {
126
                return $this->request;
1✔
127
        }
128

129

130
        /**
131
         * Returns self.
132
         */
133
        final public function getPresenter(): static
134
        {
135
                return $this;
1✔
136
        }
137

138

139
        final public function getPresenterIfExists(): static
140
        {
141
                return $this;
1✔
142
        }
143

144

145
        /** @deprecated */
146
        final public function hasPresenter(): bool
147
        {
148
                return true;
×
149
        }
150

151

152
        /**
153
         * Returns a name that uniquely identifies component.
154
         */
155
        public function getUniqueId(): string
156
        {
157
                return '';
1✔
158
        }
159

160

161
        public function isModuleCurrent(string $module): bool
1✔
162
        {
163
                $current = Helpers::splitName($this->getName())[0];
1✔
164
                return str_starts_with($current . ':', ltrim($module . ':', ':'));
1✔
165
        }
166

167

168
        public function isForwarded(): bool
169
        {
170
                return $this->forwarded || $this->request->isMethod($this->request::FORWARD);
1✔
171
        }
172

173

174
        /********************* interface IPresenter ****************d*g**/
175

176

177
        public function run(Application\Request $request): Application\Response
1✔
178
        {
179
                $this->request = $request;
1✔
180
                $this->setParent($this->getParent(), $request->getPresenterName());
1✔
181

182
                if (!$this->httpResponse->isSent()) {
1✔
183
                        $this->httpResponse->addHeader('Vary', 'X-Requested-With');
1✔
184
                }
185

186
                $this->initGlobalParameters();
1✔
187

188
                try {
189
                        // CHECK REQUIREMENTS
190
                        (new AccessPolicy(static::getReflection()))->checkAccess($this);
1✔
191
                        $this->checkRequirements(static::getReflection());
1✔
192
                        $this->checkHttpMethod();
1✔
193

194
                        // STARTUP
195
                        Arrays::invoke($this->onStartup, $this);
1✔
196
                        $this->startup();
1✔
197
                        if (!$this->startupCheck) {
1✔
198
                                $class = static::getReflection()->getMethod('startup')->getDeclaringClass()->getName();
×
199
                                throw new Nette\InvalidStateException("Method $class::startup() or its parents doesn't call parent::startup().");
×
200
                        }
201

202
                        // calls $this->action<Action>()
203
                        try {
204
                                actionMethod:
205
                                $this->tryCall(static::formatActionMethod($this->action), $this->params);
1✔
206
                        } catch (Application\SwitchException $e) {
1✔
207
                                $this->changeAction($e->getMessage());
1✔
208
                                $this->autoCanonicalize = false;
1✔
209
                                goto actionMethod;
1✔
210
                        }
211

212
                        // autoload components
213
                        foreach ($this->globalParams as $id => $foo) {
1✔
214
                                $this->getComponent((string) $id, throw: false);
1✔
215
                        }
216

217
                        if ($this->autoCanonicalize) {
1✔
218
                                $this->canonicalize();
1✔
219
                        }
220

221
                        if ($this->httpRequest->isMethod('head')) {
1✔
222
                                $this->terminate();
×
223
                        }
224

225
                        // SIGNAL HANDLING
226
                        // calls $this->handle<Signal>()
227
                        $this->processSignal();
1✔
228

229
                        // RENDERING VIEW
230
                        $this->beforeRender();
1✔
231
                        Arrays::invoke($this->onRender, $this);
1✔
232
                        // calls $this->render<View>()
233
                        try {
234
                                renderMethod:
235
                                $this->tryCall(static::formatRenderMethod($this->view), $this->params);
1✔
236
                        } catch (Application\SwitchException $e) {
1✔
237
                                $this->setView($e->getMessage());
×
238
                                goto renderMethod;
×
239
                        }
240
                        $this->afterRender();
1✔
241

242
                        // finish template rendering
243
                        $this->sendTemplate();
1✔
244

245
                } catch (Application\SwitchException $e) {
1✔
246
                        throw new \LogicException('Switch is only allowed inside action*() or render*() method.', 0, $e);
×
247
                } catch (Application\AbortException) {
1✔
248
                }
249

250
                // save component tree persistent state
251
                $this->saveGlobalState();
1✔
252

253
                if ($this->isAjax()) {
1✔
254
                        $this->getPayload()->state = $this->getGlobalState();
1✔
255
                        try {
256
                                if ($this->response instanceof Responses\TextResponse && $this->isControlInvalid()) {
1✔
257
                                        $this->snippetMode = true;
×
258
                                        $this->response->send($this->httpRequest, $this->httpResponse);
×
259
                                        $this->sendPayload();
1✔
260
                                }
261
                        } catch (Application\AbortException) {
×
262
                        }
263
                }
264

265
                if ($this->hasFlashSession()) {
1✔
266
                        $this->getFlashSession()->setExpiration('30 seconds');
×
267
                }
268

269
                if (!$this->response) {
1✔
270
                        $this->response = new Responses\VoidResponse;
1✔
271
                }
272

273
                Arrays::invoke($this->onShutdown, $this, $this->response);
1✔
274
                $this->shutdown($this->response);
1✔
275

276
                return $this->response;
1✔
277
        }
278

279

280
        /**
281
         * @return void
282
         */
283
        protected function startup()
284
        {
285
                $this->startupCheck = true;
1✔
286
        }
1✔
287

288

289
        /**
290
         * Common render method.
291
         * @return void
292
         */
293
        protected function beforeRender()
294
        {
295
        }
1✔
296

297

298
        /**
299
         * Common render method.
300
         */
301
        protected function afterRender(): void
302
        {
303
        }
1✔
304

305

306
        protected function shutdown(Application\Response $response): void
1✔
307
        {
308
        }
1✔
309

310

311
        /**
312
         * This method will be called when CSRF is detected.
313
         */
314
        public function detectedCsrf(): void
315
        {
316
                try {
317
                        $this->redirect('this');
1✔
318
                } catch (InvalidLinkException $e) {
1✔
319
                        $this->error($e->getMessage());
×
320
                }
321
        }
322

323

324
        /** @deprecated  use #[Requires(methods: ...)] to specify allowed methods */
325
        protected function checkHttpMethod(): void
326
        {
327
                if ($this->allowedMethods &&
1✔
328
                        !in_array($method = $this->httpRequest->getMethod(), $this->allowedMethods, strict: true)
1✔
329
                ) {
330
                        $this->httpResponse->setHeader('Allow', implode(',', $this->allowedMethods));
×
331
                        $this->error("Method $method is not allowed", Nette\Http\IResponse::S405_MethodNotAllowed);
×
332
                }
333
        }
1✔
334

335

336
        /********************* signal handling ****************d*g**/
337

338

339
        /**
340
         * @throws BadSignalException
341
         */
342
        public function processSignal(): void
343
        {
344
                if (!isset($this->signal)) {
1✔
345
                        return;
1✔
346
                }
347

348
                $component = $this->signalReceiver === ''
1✔
349
                        ? $this
1✔
350
                        : $this->getComponent($this->signalReceiver, throw: false);
×
351
                if ($component === null) {
1✔
352
                        throw new BadSignalException("The signal receiver component '$this->signalReceiver' is not found.");
×
353

354
                } elseif (!$component instanceof SignalReceiver) {
1✔
355
                        throw new BadSignalException("The signal receiver component '$this->signalReceiver' is not SignalReceiver implementor.");
×
356
                }
357

358
                $component->signalReceived($this->signal);
1✔
359
                $this->signal = null;
×
360
        }
361

362

363
        /**
364
         * Returns pair signal receiver and name.
365
         */
366
        final public function getSignal(): ?array
367
        {
368
                return $this->signal === null ? null : [$this->signalReceiver, $this->signal];
1✔
369
        }
370

371

372
        /**
373
         * Checks if the signal receiver is the given one.
374
         */
375
        final public function isSignalReceiver(
376
                Nette\ComponentModel\Component|string $component,
377
                string|bool|null $signal = null,
378
        ): bool
379
        {
380
                if ($component instanceof Nette\ComponentModel\Component) {
×
381
                        $component = $component === $this
×
382
                                ? ''
×
383
                                : $component->lookupPath(self::class);
×
384
                }
385

386
                if ($this->signal === null) {
×
387
                        return false;
×
388

389
                } elseif ($signal === true) {
×
390
                        return $component === ''
×
391
                                || strncmp($this->signalReceiver . '-', $component . '-', strlen($component) + 1) === 0;
×
392

393
                } elseif ($signal === null) {
×
394
                        return $this->signalReceiver === $component;
×
395
                }
396

397
                return $this->signalReceiver === $component && strcasecmp($signal, $this->signal) === 0;
×
398
        }
399

400

401
        /********************* rendering ****************d*g**/
402

403

404
        /**
405
         * Returns current action name.
406
         */
407
        final public function getAction(bool $fullyQualified = false): string
1✔
408
        {
409
                return $fullyQualified
1✔
410
                        ? ':' . $this->getName() . ':' . $this->action
×
411
                        : $this->action;
1✔
412
        }
413

414

415
        /**
416
         * Changes current action.
417
         */
418
        public function changeAction(string $action): void
1✔
419
        {
420
                $this->forwarded = true;
1✔
421
                $this->action = $this->view = $action;
1✔
422
        }
1✔
423

424

425
        /**
426
         * Switch from current action or render method to another.
427
         */
428
        public function switch(string $action): never
1✔
429
        {
430
                throw new Application\SwitchException($action);
1✔
431
        }
×
432

433

434
        /**
435
         * Returns current view.
436
         */
437
        final public function getView(): string
438
        {
439
                return $this->view;
1✔
440
        }
441

442

443
        /**
444
         * Changes current view. Any name is allowed.
445
         */
446
        public function setView(string $view): static
1✔
447
        {
448
                $this->forwarded = true;
1✔
449
                $this->view = $view;
1✔
450
                return $this;
1✔
451
        }
452

453

454
        /**
455
         * Returns current layout name.
456
         */
457
        final public function getLayout(): string|bool
458
        {
459
                return $this->layout;
×
460
        }
461

462

463
        /**
464
         * Changes or disables layout.
465
         */
466
        public function setLayout(string|bool $layout): static
1✔
467
        {
468
                $this->layout = $layout === false ? false : (string) $layout;
1✔
469
                return $this;
1✔
470
        }
471

472

473
        /**
474
         * @throws Nette\Application\AbortException
475
         * @return never
476
         */
477
        public function sendTemplate(?Template $template = null): void
1✔
478
        {
479
                $template ??= $this->getTemplate();
1✔
480
                foreach ($this->getReflection()->getTemplateVariables($this) as $name) {
1✔
481
                        $template->$name ??= $this->$name;
1✔
482
                }
483
                if ($template->getFile() === null) {
1✔
484
                        $template->setFile($this->findTemplateFile());
×
485
                }
486
                $this->sendResponse(new Responses\TextResponse($template));
1✔
487
        }
488

489

490
        /**
491
         * Finds template file name.
492
         */
493
        public function findTemplateFile(): string
494
        {
495
                $files = $this->formatTemplateFiles();
×
496
                foreach ($files as $file) {
×
497
                        if (is_file($file)) {
×
498
                                return $file;
×
499
                        }
500
                }
501

502
                $file = strtr(Arrays::first($files), '/', DIRECTORY_SEPARATOR);
×
503
                $this->error("Page not found. Missing template '$file'.");
×
504
        }
505

506

507
        /**
508
         * Finds layout template file name.
509
         * @internal
510
         */
511
        public function findLayoutTemplateFile(): ?string
512
        {
513
                if ($this->layout === false) {
×
514
                        return null;
×
515
                }
516

517
                $files = $this->formatLayoutTemplateFiles();
×
518
                foreach ($files as $file) {
×
519
                        if (is_file($file)) {
×
520
                                return $file;
×
521
                        }
522
                }
523

524
                if ($this->layout) {
×
525
                        $file = strtr(Arrays::first($files), '/', DIRECTORY_SEPARATOR);
×
526
                        throw new Nette\FileNotFoundException("Layout not found. Missing template '$file'.");
×
527
                }
528

529
                return null;
×
530
        }
531

532

533
        /**
534
         * Formats layout template file names.
535
         * @return string[]
536
         */
537
        public function formatLayoutTemplateFiles(): array
538
        {
539
                if (preg_match('#/|\\\#', (string) $this->layout)) {
1✔
540
                        return [$this->layout];
1✔
541
                }
542

543
                $layout = $this->layout ?: 'layout';
1✔
544
                $dir = dirname(static::getReflection()->getFileName());
1✔
545
                $levels = substr_count($this->getName(), ':');
1✔
546
                if (!is_dir("$dir/templates")) {
1✔
547
                        $dir = dirname($origDir = $dir);
1✔
548
                        if (!is_dir("$dir/templates")) {
1✔
549
                                $list = ["$origDir/@$layout.latte"];
1✔
550
                                do {
551
                                        $list[] = "$dir/@$layout.latte";
1✔
552
                                } while ($levels-- && ($dir = dirname($dir)));
1✔
553
                                return $list;
1✔
554
                        }
555
                }
556

557
                [, $presenter] = Helpers::splitName($this->getName());
1✔
558
                $list = [
1✔
559
                        "$dir/templates/$presenter/@$layout.latte",
1✔
560
                        "$dir/templates/$presenter.@$layout.latte",
1✔
561
                ];
562
                do {
563
                        $list[] = "$dir/templates/@$layout.latte";
1✔
564
                } while ($levels-- && ($dir = dirname($dir)));
1✔
565

566
                return $list;
1✔
567
        }
568

569

570
        /**
571
         * Formats view template file names.
572
         * @return string[]
573
         */
574
        public function formatTemplateFiles(): array
575
        {
576
                $dir = dirname(static::getReflection()->getFileName());
1✔
577
                if (!is_dir("$dir/templates")) {
1✔
578
                        $dir = dirname($origDir = $dir);
1✔
579
                        if (!is_dir("$dir/templates")) {
1✔
580
                                return [
581
                                        "$origDir/$this->view.latte",
1✔
582
                                ];
583
                        }
584
                }
585

586
                [, $presenter] = Helpers::splitName($this->getName());
1✔
587
                return [
588
                        "$dir/templates/$presenter/$this->view.latte",
1✔
589
                        "$dir/templates/$presenter.$this->view.latte",
1✔
590
                ];
591
        }
592

593

594
        /**
595
         * Formats action method name.
596
         */
597
        public static function formatActionMethod(string $action): string
1✔
598
        {
599
                return 'action' . ucfirst($action);
1✔
600
        }
601

602

603
        /**
604
         * Formats render view method name.
605
         */
606
        public static function formatRenderMethod(string $view): string
1✔
607
        {
608
                return 'render' . ucfirst($view);
1✔
609
        }
610

611

612
        protected function createTemplate(?string $class = null): Template
1✔
613
        {
614
                $class ??= $this->formatTemplateClass();
1✔
615
                return $this->getTemplateFactory()->createTemplate($this, $class);
1✔
616
        }
617

618

619
        public function formatTemplateClass(): ?string
620
        {
621
                $base = preg_replace('#Presenter$#', '', static::class);
1✔
622
                return $this->checkTemplateClass($base . ucfirst($this->action) . 'Template')
1✔
623
                        ?? $this->checkTemplateClass($base . 'Template');
1✔
624
        }
625

626

627
        /********************* partial AJAX rendering ****************d*g**/
628

629

630
        final public function getPayload(): \stdClass
631
        {
632
                return $this->payload ??= new \stdClass;
1✔
633
        }
634

635

636
        /**
637
         * Is AJAX request?
638
         */
639
        public function isAjax(): bool
640
        {
641
                if (!isset($this->ajaxMode)) {
1✔
642
                        $this->ajaxMode = $this->httpRequest->isAjax();
1✔
643
                }
644

645
                return $this->ajaxMode;
1✔
646
        }
647

648

649
        /**
650
         * Sends AJAX payload to the output.
651
         * @throws Nette\Application\AbortException
652
         * @return never
653
         */
654
        public function sendPayload(): void
655
        {
656
                $this->sendResponse(new Responses\JsonResponse($this->getPayload()));
×
657
        }
658

659

660
        /**
661
         * Sends JSON data to the output.
662
         * @throws Nette\Application\AbortException
663
         * @return never
664
         */
665
        public function sendJson(mixed $data): void
666
        {
667
                $this->sendResponse(new Responses\JsonResponse($data));
×
668
        }
669

670

671
        /********************* navigation & flow ****************d*g**/
672

673

674
        /**
675
         * Sends response and terminates presenter.
676
         * @throws Nette\Application\AbortException
677
         * @return never
678
         */
679
        public function sendResponse(Application\Response $response): void
1✔
680
        {
681
                $this->response = $response;
1✔
682
                $this->terminate();
1✔
683
        }
684

685

686
        /**
687
         * Correctly terminates presenter.
688
         * @throws Nette\Application\AbortException
689
         * @return never
690
         */
691
        public function terminate(): void
692
        {
693
                throw new Application\AbortException;
1✔
694
        }
695

696

697
        /**
698
         * Forward to another presenter or action.
699
         * @param  array|mixed  $args
700
         * @throws Nette\Application\AbortException
701
         * @return never
702
         */
703
        public function forward(string|Nette\Application\Request $destination, $args = []): void
1✔
704
        {
705
                if ($destination instanceof Application\Request) {
1✔
706
                        $this->sendResponse(new Responses\ForwardResponse($destination));
×
707
                }
708

709
                $args = func_num_args() < 3 && is_array($args)
1✔
710
                        ? $args
1✔
711
                        : array_slice(func_get_args(), 1);
×
712
                $request = $this->linkGenerator->createRequest($this, $destination, $args, 'forward');
1✔
713
                $this->sendResponse(new Responses\ForwardResponse($request));
1✔
714
        }
715

716

717
        /**
718
         * Redirect to another URL and ends presenter execution.
719
         * @throws Nette\Application\AbortException
720
         * @return never
721
         */
722
        public function redirectUrl(string $url, ?int $httpCode = null): void
1✔
723
        {
724
                if ($this->isAjax()) {
1✔
725
                        $this->getPayload()->redirect = $url;
×
726
                        $this->sendPayload();
×
727

728
                } elseif (!$httpCode) {
1✔
729
                        $httpCode = $this->httpRequest->isMethod('post')
1✔
730
                                ? Http\IResponse::S303_PostGet
×
731
                                : Http\IResponse::S302_Found;
1✔
732
                }
733

734
                $this->sendResponse(new Responses\RedirectResponse($url, $httpCode));
1✔
735
        }
736

737

738
        /**
739
         * Returns the last created Request.
740
         * @internal
741
         */
742
        final public function getLastCreatedRequest(): ?Application\Request
743
        {
744
                return $this->linkGenerator->lastRequest;
1✔
745
        }
746

747

748
        /**
749
         * Returns the last created Request flag.
750
         * @internal
751
         */
752
        final public function getLastCreatedRequestFlag(string $flag): bool
1✔
753
        {
754
                return (bool) $this->linkGenerator->lastRequest?->hasFlag($flag);
1✔
755
        }
756

757

758
        /**
759
         * Conditional redirect to canonicalized URI.
760
         * @param  mixed  ...$args
761
         * @throws Nette\Application\AbortException
762
         */
763
        public function canonicalize(?string $destination = null, ...$args): void
1✔
764
        {
765
                $request = $this->request;
1✔
766
                if ($this->isAjax() || (!$request->isMethod('get') && !$request->isMethod('head'))) {
1✔
767
                        return;
1✔
768
                }
769

770
                $args = count($args) === 1 && is_array($args[0] ?? null)
1✔
771
                        ? $args[0]
×
772
                        : $args;
1✔
773
                try {
774
                        $url = $this->linkGenerator->link(
1✔
775
                                $destination ?: $this->action,
1✔
776
                                $args + $this->getGlobalState() + $request->getParameters(),
1✔
777
                                $this,
778
                                'redirectX',
1✔
779
                        );
780
                } catch (InvalidLinkException) {
×
781
                }
782

783
                if (!isset($url) || $this->httpRequest->getUrl()->isEqual($url)) {
1✔
784
                        return;
×
785
                }
786

787
                $code = $request->hasFlag($request::VARYING)
1✔
788
                        ? Http\IResponse::S302_Found
×
789
                        : Http\IResponse::S301_MovedPermanently;
1✔
790
                $this->sendResponse(new Responses\RedirectResponse($url, $code));
1✔
791
        }
792

793

794
        /**
795
         * Attempts to cache the sent entity by its last modification date.
796
         * @param  ?string  $etag  strong entity tag validator
797
         * @param  ?string  $expire  like '20 minutes'
798
         * @throws Nette\Application\AbortException
799
         */
800
        public function lastModified(
801
                string|int|\DateTimeInterface|null $lastModified,
802
                ?string $etag = null,
803
                ?string $expire = null,
804
        ): void
805
        {
806
                if ($expire !== null) {
×
807
                        $this->httpResponse->setExpiration($expire);
×
808
                }
809

810
                $helper = new Http\Context($this->httpRequest, $this->httpResponse);
×
811
                if (!$helper->isModified($lastModified, $etag)) {
×
812
                        $this->terminate();
×
813
                }
814
        }
815

816

817
        /** @deprecated @internal */
818
        protected function createRequest(Component $component, string $destination, array $args, string $mode): ?string
819
        {
820
                return $this->linkGenerator->link($destination, $args, $component, $mode);
×
821
        }
822

823

824
        /** @deprecated @internal */
825
        public static function parseDestination(string $destination): array
826
        {
827
                return LinkGenerator::parseDestination($destination);
×
828
        }
829

830

831
        /** @deprecated @internal */
832
        protected function requestToUrl(Application\Request $request, ?bool $relative = null): string
833
        {
834
                return $this->linkGenerator->requestToUrl($request, $relative ?? !$this->absoluteUrls);
×
835
        }
836

837

838
        /**
839
         * Invalid link handler. Descendant can override this method to change default behaviour.
840
         * @throws InvalidLinkException
841
         */
842
        protected function handleInvalidLink(InvalidLinkException $e): string
1✔
843
        {
844
                if ($this->invalidLinkMode & self::InvalidLinkException) {
1✔
845
                        throw $e;
1✔
846
                } elseif ($this->invalidLinkMode & self::InvalidLinkWarning) {
1✔
847
                        trigger_error('Invalid link: ' . $e->getMessage(), E_USER_WARNING);
1✔
848
                }
849

850
                return $this->invalidLinkMode & self::InvalidLinkTextual
1✔
851
                        ? '#error: ' . $e->getMessage()
1✔
852
                        : '#';
1✔
853
        }
854

855

856
        /********************* request serialization ****************d*g**/
857

858

859
        /**
860
         * Stores current request to session.
861
         */
862
        public function storeRequest(string $expiration = '+ 10 minutes'): string
1✔
863
        {
864
                $session = $this->getSession('Nette.Application/requests');
1✔
865
                do {
866
                        $key = Nette\Utils\Random::generate(5);
1✔
867
                } while ($session->get($key));
1✔
868

869
                $session->set($key, [$this->user?->getId(), $this->request]);
1✔
870
                $session->setExpiration($expiration, $key);
1✔
871
                return $key;
1✔
872
        }
873

874

875
        /**
876
         * Restores request from session.
877
         */
878
        public function restoreRequest(string $key): void
879
        {
880
                $session = $this->getSession('Nette.Application/requests');
×
881
                $data = $session->get($key);
×
882
                if (!$data || ($data[0] !== null && $data[0] !== $this->getUser()->getId())) {
×
883
                        return;
×
884
                }
885

886
                $request = clone $data[1];
×
887
                $session->remove($key);
×
888
                $params = $request->getParameters();
×
889
                $params[self::FlashKey] = $this->getFlashKey();
×
890
                $request->setParameters($params);
×
891
                if ($request->isMethod('POST')) {
×
892
                        $request->setFlag(Application\Request::RESTORED, true);
×
893
                        $this->sendResponse(new Responses\ForwardResponse($request));
×
894
                } else {
895
                        $this->redirectUrl($this->linkGenerator->requestToUrl($request));
×
896
                }
897
        }
898

899

900
        /********************* interface StatePersistent ****************d*g**/
901

902

903
        /**
904
         * Descendant can override this method to return the names of custom persistent components.
905
         * @return string[]
906
         */
907
        public static function getPersistentComponents(): array
908
        {
909
                return [];
1✔
910
        }
911

912

913
        /**
914
         * Saves state information for all subcomponents to $this->globalState.
915
         */
916
        public function getGlobalState(?string $forClass = null): array
1✔
917
        {
918
                $sinces = &$this->globalStateSinces;
1✔
919

920
                if (!isset($this->globalState)) {
1✔
921
                        $state = [];
1✔
922
                        foreach ($this->globalParams as $id => $params) {
1✔
923
                                $prefix = $id . self::NameSeparator;
1✔
924
                                foreach ($params as $key => $val) {
1✔
925
                                        $state[$prefix . $key] = $val;
1✔
926
                                }
927
                        }
928

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

931
                        if ($sinces === null) {
1✔
932
                                $sinces = [];
1✔
933
                                foreach ($this->getReflection()->getPersistentParams() as $name => $meta) {
1✔
934
                                        $sinces[$name] = $meta['since'];
1✔
935
                                }
936
                        }
937

938
                        $persistents = $this->getReflection()->getPersistentComponents();
1✔
939

940
                        foreach ($this->getComponentTree() as $component) {
1✔
941
                                if ($component->getParent() === $this) {
1✔
942
                                        // counts on child-first search
943
                                        $since = $persistents[$component->getName()]['since'] ?? false; // false = nonpersistent
1✔
944
                                }
945

946
                                if (!$component instanceof StatePersistent) {
1✔
947
                                        continue;
×
948
                                }
949

950
                                $prefix = $component->getUniqueId() . self::NameSeparator;
1✔
951
                                $params = [];
1✔
952
                                $component->saveState($params);
1✔
953
                                foreach ($params as $key => $val) {
1✔
954
                                        $state[$prefix . $key] = $val;
1✔
955
                                        $sinces[$prefix . $key] = $since;
1✔
956
                                }
957
                        }
958
                } else {
959
                        $state = $this->globalState;
1✔
960
                }
961

962
                if ($forClass !== null) {
1✔
963
                        $tree = Helpers::getClassesAndTraits($forClass);
1✔
964
                        $since = null;
1✔
965
                        foreach ($state as $key => $foo) {
1✔
966
                                if (!isset($sinces[$key])) {
1✔
967
                                        $x = strpos($key, self::NameSeparator);
1✔
968
                                        $x = $x === false ? $key : substr($key, 0, $x);
1✔
969
                                        $sinces[$key] = $sinces[$x] ?? false;
1✔
970
                                }
971

972
                                if ($since !== $sinces[$key]) {
1✔
973
                                        $since = $sinces[$key];
1✔
974
                                        $ok = $since && isset($tree[$since]);
1✔
975
                                }
976

977
                                if (!$ok) {
1✔
978
                                        unset($state[$key]);
1✔
979
                                }
980
                        }
981
                }
982

983
                return $state;
1✔
984
        }
985

986

987
        /**
988
         * Permanently saves state information for all subcomponents to $this->globalState.
989
         */
990
        protected function saveGlobalState(): void
991
        {
992
                $this->globalParams = [];
1✔
993
                $this->globalState = $this->getGlobalState();
1✔
994
        }
1✔
995

996

997
        /**
998
         * Initializes $this->globalParams, $this->signal & $this->signalReceiver, $this->action, $this->view. Called by run().
999
         * @throws Nette\Application\BadRequestException if action name is not valid
1000
         */
1001
        private function initGlobalParameters(): void
1002
        {
1003
                // init $this->globalParams
1004
                $this->globalParams = [];
1✔
1005
                $selfParams = [];
1✔
1006

1007
                $params = $this->request->getParameters();
1✔
1008
                if (($tmp = $this->request->getPost('_' . self::SignalKey)) !== null) {
1✔
1009
                        $params[self::SignalKey] = $tmp;
1✔
1010
                } elseif ($this->isAjax()) {
1✔
1011
                        $params += $this->request->getPost();
1✔
1012
                        if (($tmp = $this->request->getPost(self::SignalKey)) !== null) {
1✔
1013
                                $params[self::SignalKey] = $tmp;
1✔
1014
                        }
1015
                }
1016

1017
                foreach ($params as $key => $value) {
1✔
1018
                        if (!preg_match('#^((?:[a-z0-9_]+-)*)((?!\d+$)[a-z0-9_]+)$#Di', (string) $key, $matches)) {
1✔
1019
                                continue;
×
1020
                        } elseif (!$matches[1]) {
1✔
1021
                                $selfParams[$key] = $value;
1✔
1022
                        } else {
1023
                                $this->globalParams[substr($matches[1], 0, -1)][$matches[2]] = $value;
1✔
1024
                        }
1025
                }
1026

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

1033
                $this->changeAction($action);
1✔
1034
                $this->forwarded = false;
1✔
1035

1036
                // init $this->signalReceiver and key 'signal' in appropriate params array
1037
                $this->signalReceiver = $this->getUniqueId();
1✔
1038
                if (isset($selfParams[self::SignalKey])) {
1✔
1039
                        $param = $selfParams[self::SignalKey];
1✔
1040
                        if (!is_string($param)) {
1✔
1041
                                $this->error('Signal name is not string.');
1✔
1042
                        }
1043

1044
                        $pos = strrpos($param, '-');
1✔
1045
                        if ($pos) {
1✔
1046
                                $this->signalReceiver = substr($param, 0, $pos);
1✔
1047
                                $this->signal = substr($param, $pos + 1);
1✔
1048
                        } else {
1049
                                $this->signalReceiver = $this->getUniqueId();
1✔
1050
                                $this->signal = $param;
1✔
1051
                        }
1052

1053
                        if ($this->signal === '') {
1✔
1054
                                $this->signal = null;
×
1055
                        }
1056
                }
1057

1058
                $this->loadState($selfParams);
1✔
1059
        }
1✔
1060

1061

1062
        /**
1063
         * Pops parameters for specified component.
1064
         * @internal
1065
         */
1066
        final public function popGlobalParameters(string $id): array
1✔
1067
        {
1068
                $res = $this->globalParams[$id] ?? [];
1✔
1069
                unset($this->globalParams[$id]);
1✔
1070
                return $res;
1✔
1071
        }
1072

1073

1074
        /********************* flash session ****************d*g**/
1075

1076

1077
        private function getFlashKey(): ?string
1078
        {
1079
                $flashKey = $this->getParameter(self::FlashKey);
1✔
1080
                return is_string($flashKey) && $flashKey !== ''
1✔
1081
                        ? $flashKey
×
1082
                        : null;
1✔
1083
        }
1084

1085

1086
        /**
1087
         * Checks if a flash session namespace exists.
1088
         */
1089
        public function hasFlashSession(): bool
1090
        {
1091
                $flashKey = $this->getFlashKey();
1✔
1092
                return $flashKey !== null
1✔
1093
                        && $this->getSession()->hasSection('Nette.Application.Flash/' . $flashKey);
1✔
1094
        }
1095

1096

1097
        /**
1098
         * Returns session namespace provided to pass temporary data between redirects.
1099
         */
1100
        public function getFlashSession(): Http\SessionSection
1101
        {
1102
                $flashKey = $this->getFlashKey();
×
1103
                if ($flashKey === null) {
×
1104
                        $this->params[self::FlashKey] = $flashKey = Nette\Utils\Random::generate(4);
×
1105
                }
1106

1107
                return $this->getSession('Nette.Application.Flash/' . $flashKey);
×
1108
        }
1109

1110

1111
        /********************* services ****************d*g**/
1112

1113

1114
        final public function injectPrimary(
1✔
1115
                Http\IRequest $httpRequest,
1116
                Http\IResponse $httpResponse,
1117
                ?Application\IPresenterFactory $presenterFactory = null,
1118
                ?Nette\Routing\Router $router = null,
1119
                ?Http\Session $session = null,
1120
                ?Nette\Security\User $user = null,
1121
                ?TemplateFactory $templateFactory = null,
1122
        ): void
1123
        {
1124
                $this->httpRequest = $httpRequest;
1✔
1125
                $this->httpResponse = $httpResponse;
1✔
1126
                $this->session = $session;
1✔
1127
                $this->user = $user;
1✔
1128
                $this->templateFactory = $templateFactory;
1✔
1129
                if ($router && $presenterFactory) {
1✔
1130
                        $url = $httpRequest->getUrl();
1✔
1131
                        $this->linkGenerator = new LinkGenerator(
1✔
1132
                                $router,
1✔
1133
                                new Http\UrlScript($url->getHostUrl() . $url->getScriptPath()),
1✔
1134
                                $presenterFactory,
1135
                        );
1136
                }
1137
        }
1✔
1138

1139

1140
        final public function getHttpRequest(): Http\IRequest
1141
        {
1142
                return $this->httpRequest;
1✔
1143
        }
1144

1145

1146
        final public function getHttpResponse(): Http\IResponse
1147
        {
1148
                return $this->httpResponse;
1✔
1149
        }
1150

1151

1152
        final public function getSession(?string $namespace = null): Http\Session|Http\SessionSection
1✔
1153
        {
1154
                if (empty($this->session)) {
1✔
1155
                        throw new Nette\InvalidStateException('Service Session has not been set.');
×
1156
                }
1157

1158
                return $namespace === null
1✔
1159
                        ? $this->session
×
1160
                        : $this->session->getSection($namespace);
1✔
1161
        }
1162

1163

1164
        final public function getUser(): Nette\Security\User
1165
        {
1166
                return $this->user ?? throw new Nette\InvalidStateException('Service User has not been set.');
×
1167
        }
1168

1169

1170
        final public function getTemplateFactory(): TemplateFactory
1171
        {
1172
                return $this->templateFactory ?? throw new Nette\InvalidStateException('Service TemplateFactory has not been set.');
1✔
1173
        }
1174

1175

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