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

nette / application / 20921730768

12 Jan 2026 01:49PM UTC coverage: 84.059% (+0.02%) from 84.039%
20921730768

push

github

dg
normalized callable to Closure

5 of 5 new or added lines in 3 files covered. (100.0%)

134 existing lines in 9 files now uncovered.

2009 of 2390 relevant lines covered (84.06%)

0.84 hits per line

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

82.25
/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

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

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

122

123
        public function __construct()
124
        {
125
        }
1✔
126

127

128
        final public function getRequest(): ?Application\Request
129
        {
130
                return $this->request;
1✔
131
        }
132

133

134
        /**
135
         * Returns self.
136
         */
137
        final public function getPresenter(): static
138
        {
139
                return $this;
1✔
140
        }
141

142

143
        final public function getPresenterIfExists(): static
144
        {
145
                return $this;
1✔
146
        }
147

148

149
        /** @deprecated */
150
        final public function hasPresenter(): bool
151
        {
UNCOV
152
                return true;
×
153
        }
154

155

156
        /**
157
         * Returns a name that uniquely identifies component.
158
         */
159
        public function getUniqueId(): string
160
        {
161
                return '';
1✔
162
        }
163

164

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

171

172
        public function isForwarded(): bool
173
        {
174
                return $this->forwarded || $this->request->isMethod($this->request::FORWARD);
1✔
175
        }
176

177

178
        /********************* interface IPresenter ****************d*g**/
179

180

181
        public function run(Application\Request $request): Application\Response
1✔
182
        {
183
                $this->request = $request;
1✔
184
                $this->setParent($this->getParent(), $request->getPresenterName());
1✔
185

186
                if (!$this->httpResponse->isSent()) {
1✔
187
                        $this->httpResponse->addHeader('Vary', 'X-Requested-With');
1✔
188
                }
189

190
                $this->initGlobalParameters();
1✔
191

192
                try {
193
                        // CHECK REQUIREMENTS
194
                        (new AccessPolicy(static::getReflection()))->checkAccess($this);
1✔
195
                        $this->checkRequirements(static::getReflection());
1✔
196
                        $this->checkHttpMethod();
1✔
197

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

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

216
                        // autoload components
217
                        foreach ($this->globalParams as $id => $foo) {
1✔
218
                                $this->getComponent((string) $id, throw: false);
1✔
219
                        }
220

221
                        if ($this->autoCanonicalize) {
1✔
222
                                $this->canonicalize();
1✔
223
                        }
224

225
                        if ($this->httpRequest->isMethod('head')) {
1✔
UNCOV
226
                                $this->terminate();
×
227
                        }
228

229
                        // SIGNAL HANDLING
230
                        // calls $this->handle<Signal>()
231
                        $this->processSignal();
1✔
232

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

246
                        // finish template rendering
247
                        $this->sendTemplate();
1✔
248

249
                } catch (Application\SwitchException $e) {
1✔
UNCOV
250
                        throw new \LogicException('Switch is only allowed inside action*() or render*() method.', 0, $e);
×
251
                } catch (Application\AbortException) {
1✔
252
                }
253

254
                // save component tree persistent state
255
                $this->saveGlobalState();
1✔
256

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

269
                if ($this->hasFlashSession()) {
1✔
270
                        $this->getFlashSession()->setExpiration('30 seconds');
1✔
271
                }
272

273
                if (!$this->response) {
1✔
274
                        $this->response = new Responses\VoidResponse;
1✔
275
                }
276

277
                Arrays::invoke($this->onShutdown, $this, $this->response);
1✔
278
                $this->shutdown($this->response);
1✔
279

280
                return $this->response;
1✔
281
        }
282

283

284
        /**
285
         * @return void
286
         */
287
        protected function startup()
288
        {
289
                $this->startupCheck = true;
1✔
290
        }
1✔
291

292

293
        /**
294
         * Common render method.
295
         * @return void
296
         */
297
        protected function beforeRender()
298
        {
299
        }
1✔
300

301

302
        /**
303
         * Common render method.
304
         */
305
        protected function afterRender(): void
306
        {
307
        }
1✔
308

309

310
        protected function shutdown(Application\Response $response): void
1✔
311
        {
312
        }
1✔
313

314

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

327

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

339

340
        /********************* signal handling ****************d*g**/
341

342

343
        /**
344
         * @throws BadSignalException
345
         */
346
        public function processSignal(): void
347
        {
348
                if (!isset($this->signal)) {
1✔
349
                        return;
1✔
350
                }
351

352
                $component = $this->signalReceiver === ''
1✔
353
                        ? $this
1✔
354
                        : $this->getComponent($this->signalReceiver, throw: false);
1✔
355
                if ($component === null) {
1✔
356
                        throw new BadSignalException("The signal receiver component '$this->signalReceiver' is not found.");
1✔
357

358
                } elseif (!$component instanceof SignalReceiver) {
1✔
UNCOV
359
                        throw new BadSignalException("The signal receiver component '$this->signalReceiver' is not SignalReceiver implementor.");
×
360
                }
361

362
                $component->signalReceived($this->signal);
1✔
363
                $this->signal = null;
1✔
364
        }
1✔
365

366

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

375

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

390
                if ($this->signal === null) {
×
391
                        return false;
×
392

393
                } elseif ($signal === true) {
×
394
                        return $component === ''
×
UNCOV
395
                                || strncmp($this->signalReceiver . '-', $component . '-', strlen($component) + 1) === 0;
×
396

397
                } elseif ($signal === null) {
×
UNCOV
398
                        return $this->signalReceiver === $component;
×
399
                }
400

UNCOV
401
                return $this->signalReceiver === $component && strcasecmp($signal, $this->signal) === 0;
×
402
        }
403

404

405
        /********************* rendering ****************d*g**/
406

407

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

418

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

428

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

437

438
        /**
439
         * Returns current view.
440
         */
441
        final public function getView(): string
442
        {
443
                return $this->view;
1✔
444
        }
445

446

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

457

458
        /**
459
         * Returns current layout name.
460
         */
461
        final public function getLayout(): string|bool
462
        {
UNCOV
463
                return $this->layout;
×
464
        }
465

466

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

476

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

493

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

UNCOV
506
                $file = strtr(Arrays::first($files), '/', DIRECTORY_SEPARATOR);
×
UNCOV
507
                $this->error("Page not found. Missing template '$file'.");
×
508
        }
509

510

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

UNCOV
521
                $files = $this->formatLayoutTemplateFiles();
×
UNCOV
522
                foreach ($files as $file) {
×
UNCOV
523
                        if (is_file($file)) {
×
524
                                return $file;
×
525
                        }
526
                }
527

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

UNCOV
533
                return null;
×
534
        }
535

536

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

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

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

570
                return $list;
1✔
571
        }
572

573

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

590
                [, $presenter] = Helpers::splitName($this->getName());
1✔
591
                return [
592
                        "$dir/templates/$presenter/$this->view.latte",
1✔
593
                        "$dir/templates/$presenter.$this->view.latte",
1✔
594
                ];
595
        }
596

597

598
        /**
599
         * Formats action method name.
600
         */
601
        public static function formatActionMethod(string $action): string
1✔
602
        {
603
                return 'action' . ucfirst($action);
1✔
604
        }
605

606

607
        /**
608
         * Formats render view method name.
609
         */
610
        public static function formatRenderMethod(string $view): string
1✔
611
        {
612
                return 'render' . ucfirst($view);
1✔
613
        }
614

615

616
        protected function createTemplate(?string $class = null): Template
1✔
617
        {
618
                $class ??= $this->formatTemplateClass();
1✔
619
                return $this->getTemplateFactory()->createTemplate($this, $class);
1✔
620
        }
621

622

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

630

631
        /********************* partial AJAX rendering ****************d*g**/
632

633

634
        final public function getPayload(): \stdClass
635
        {
636
                return $this->payload ??= new \stdClass;
1✔
637
        }
638

639

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

649
                return $this->ajaxMode;
1✔
650
        }
651

652

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

663

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

674

675
        /********************* navigation & flow ****************d*g**/
676

677

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

689

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

700

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

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

720

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

732
                } elseif (!$httpCode) {
1✔
733
                        $httpCode = $this->httpRequest->isMethod('post')
1✔
734
                                ? Http\IResponse::S303_PostGet
1✔
735
                                : Http\IResponse::S302_Found;
1✔
736
                }
737

738
                $this->sendResponse(new Responses\RedirectResponse($url, $httpCode));
1✔
739
        }
740

741

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

751

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

761

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

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

787
                if (!isset($url) || $this->httpRequest->getUrl()->isEqual($url)) {
1✔
UNCOV
788
                        return;
×
789
                }
790

791
                $code = $request->hasFlag($request::VARYING)
1✔
792
                        ? Http\IResponse::S302_Found
1✔
793
                        : Http\IResponse::S301_MovedPermanently;
1✔
794
                $this->sendResponse(new Responses\RedirectResponse($url, $code));
1✔
795
        }
796

797

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

UNCOV
814
                $helper = new Http\Context($this->httpRequest, $this->httpResponse);
×
UNCOV
815
                if (!$helper->isModified($lastModified, $etag)) {
×
UNCOV
816
                        $this->terminate();
×
817
                }
818
        }
819

820

821
        /** @deprecated @internal */
822
        protected function createRequest(Component $component, string $destination, array $args, string $mode): ?string
823
        {
UNCOV
824
                return $this->linkGenerator->link($destination, $args, $component, $mode);
×
825
        }
826

827

828
        /** @deprecated @internal */
829
        public static function parseDestination(string $destination): array
830
        {
UNCOV
831
                return LinkGenerator::parseDestination($destination);
×
832
        }
833

834

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

841

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

854
                return $this->invalidLinkMode & self::InvalidLinkTextual
1✔
855
                        ? '#error: ' . $e->getMessage()
1✔
856
                        : '#';
1✔
857
        }
858

859

860
        /********************* request serialization ****************d*g**/
861

862

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

873
                $session->set($key, [$this->user?->getId(), $this->request]);
1✔
874
                $session->setExpiration($expiration, $key);
1✔
875
                return $key;
1✔
876
        }
877

878

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

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

903

904
        /********************* interface StatePersistent ****************d*g**/
905

906

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

916

917
        /**
918
         * Saves state information for all subcomponents to $this->globalState.
919
         * @return array<string, mixed>
920
         */
921
        public function getGlobalState(?string $forClass = null): array
1✔
922
        {
923
                $sinces = &$this->globalStateSinces;
1✔
924

925
                if (!isset($this->globalState)) {
1✔
926
                        $state = [];
1✔
927
                        foreach ($this->globalParams as $id => $params) {
1✔
928
                                $prefix = $id . self::NameSeparator;
1✔
929
                                foreach ($params as $key => $val) {
1✔
930
                                        $state[$prefix . $key] = $val;
1✔
931
                                }
932
                        }
933

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

936
                        if ($sinces === null) {
1✔
937
                                $sinces = [];
1✔
938
                                foreach ($this->getReflection()->getPersistentParams() as $name => $meta) {
1✔
939
                                        $sinces[$name] = $meta['since'];
1✔
940
                                }
941
                        }
942

943
                        $persistents = $this->getReflection()->getPersistentComponents();
1✔
944

945
                        foreach ($this->getComponentTree() as $component) {
1✔
946
                                if ($component->getParent() === $this) {
1✔
947
                                        // counts on child-first search
948
                                        $since = $persistents[$component->getName()]['since'] ?? false; // false = nonpersistent
1✔
949
                                }
950

951
                                if (!$component instanceof StatePersistent) {
1✔
UNCOV
952
                                        continue;
×
953
                                }
954

955
                                $prefix = $component->getUniqueId() . self::NameSeparator;
1✔
956
                                $params = [];
1✔
957
                                $component->saveState($params);
1✔
958
                                foreach ($params as $key => $val) {
1✔
959
                                        $state[$prefix . $key] = $val;
1✔
960
                                        $sinces[$prefix . $key] = $since;
1✔
961
                                }
962
                        }
963
                } else {
964
                        $state = $this->globalState;
1✔
965
                }
966

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

977
                                if ($since !== $sinces[$key]) {
1✔
978
                                        $since = $sinces[$key];
1✔
979
                                        $ok = $since && isset($tree[$since]);
1✔
980
                                }
981

982
                                if (!$ok) {
1✔
983
                                        unset($state[$key]);
1✔
984
                                }
985
                        }
986
                }
987

988
                return $state;
1✔
989
        }
990

991

992
        /**
993
         * Permanently saves state information for all subcomponents to $this->globalState.
994
         */
995
        protected function saveGlobalState(): void
996
        {
997
                $this->globalParams = [];
1✔
998
                $this->globalState = $this->getGlobalState();
1✔
999
        }
1✔
1000

1001

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

1012
                $params = $this->request->getParameters();
1✔
1013
                if (($tmp = $this->request->getPost('_' . self::SignalKey)) !== null) {
1✔
1014
                        $params[self::SignalKey] = $tmp;
1✔
1015
                } elseif ($this->isAjax()) {
1✔
1016
                        $params += $this->request->getPost();
1✔
1017
                        if (($tmp = $this->request->getPost(self::SignalKey)) !== null) {
1✔
1018
                                $params[self::SignalKey] = $tmp;
1✔
1019
                        }
1020
                }
1021

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

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

1038
                $this->changeAction($action);
1✔
1039
                $this->forwarded = false;
1✔
1040

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

1049
                        $pos = strrpos($param, '-');
1✔
1050
                        if ($pos) {
1✔
1051
                                $this->signalReceiver = substr($param, 0, $pos);
1✔
1052
                                $this->signal = substr($param, $pos + 1);
1✔
1053
                        } else {
1054
                                $this->signalReceiver = $this->getUniqueId();
1✔
1055
                                $this->signal = $param;
1✔
1056
                        }
1057

1058
                        if ($this->signal === '') {
1✔
UNCOV
1059
                                $this->signal = null;
×
1060
                        }
1061
                }
1062

1063
                $this->loadState($selfParams);
1✔
1064
        }
1✔
1065

1066

1067
        /**
1068
         * Pops parameters for specified component.
1069
         * @return array<string, mixed>
1070
         * @internal
1071
         */
1072
        final public function popGlobalParameters(string $id): array
1✔
1073
        {
1074
                $res = $this->globalParams[$id] ?? [];
1✔
1075
                unset($this->globalParams[$id]);
1✔
1076
                return $res;
1✔
1077
        }
1078

1079

1080
        /********************* flash session ****************d*g**/
1081

1082

1083
        private function getFlashKey(): ?string
1084
        {
1085
                $flashKey = $this->getParameter(self::FlashKey);
1✔
1086
                return is_string($flashKey) && $flashKey !== ''
1✔
1087
                        ? $flashKey
1✔
1088
                        : null;
1✔
1089
        }
1090

1091

1092
        /**
1093
         * Checks if a flash session namespace exists.
1094
         */
1095
        public function hasFlashSession(): bool
1096
        {
1097
                $flashKey = $this->getFlashKey();
1✔
1098
                return $flashKey !== null
1✔
1099
                        && $this->getSession()->hasSection('Nette.Application.Flash/' . $flashKey);
1✔
1100
        }
1101

1102

1103
        /**
1104
         * Returns session namespace provided to pass temporary data between redirects.
1105
         */
1106
        public function getFlashSession(): Http\SessionSection
1107
        {
1108
                $flashKey = $this->getFlashKey();
1✔
1109
                if ($flashKey === null) {
1✔
1110
                        $this->params[self::FlashKey] = $flashKey = Nette\Utils\Random::generate(4);
1✔
1111
                }
1112

1113
                return $this->getSession('Nette.Application.Flash/' . $flashKey);
1✔
1114
        }
1115

1116

1117
        /********************* services ****************d*g**/
1118

1119

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

1145

1146
        final public function getHttpRequest(): Http\IRequest
1147
        {
1148
                return $this->httpRequest;
1✔
1149
        }
1150

1151

1152
        final public function getHttpResponse(): Http\IResponse
1153
        {
1154
                return $this->httpResponse;
1✔
1155
        }
1156

1157

1158
        final public function getSession(?string $namespace = null): Http\Session|Http\SessionSection
1✔
1159
        {
1160
                if (empty($this->session)) {
1✔
UNCOV
1161
                        throw new Nette\InvalidStateException('Service Session has not been set.');
×
1162
                }
1163

1164
                return $namespace === null
1✔
1165
                        ? $this->session
1✔
1166
                        : $this->session->getSection($namespace);
1✔
1167
        }
1168

1169

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

1175

1176
        final public function getTemplateFactory(): TemplateFactory
1177
        {
1178
                return $this->templateFactory ?? throw new Nette\InvalidStateException('Service TemplateFactory has not been set.');
1✔
1179
        }
1180

1181

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