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

nette / application / 20834855301

08 Jan 2026 10:54PM UTC coverage: 84.605% (+1.7%) from 82.856%
20834855301

push

github

dg
added CLAUDE.md

1940 of 2293 relevant lines covered (84.61%)

0.85 hits per line

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

81.75
/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-deprecated Nette\Application\Request $request
27
 * @property-deprecated string $action
28
 * @property-deprecated      string $view
29
 * @property-deprecated      string|bool $layout
30
 * @property-read \stdClass $payload
31
 * @property-deprecated 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
        {
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✔
202
                                $class = static::getReflection()->getMethod('startup')->getDeclaringClass()->getName();
×
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✔
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✔
241
                                $this->setView($e->getMessage());
×
242
                                goto renderMethod;
×
243
                        }
244
                        $this->afterRender();
1✔
245

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

249
                } catch (Application\SwitchException $e) {
1✔
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;
×
262
                                        $this->response->send($this->httpRequest, $this->httpResponse);
×
263
                                        $this->sendPayload();
1✔
264
                                }
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✔
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
                ) {
334
                        $this->httpResponse->setHeader('Allow', implode(',', $this->allowedMethods));
×
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✔
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
        {
384
                if ($component instanceof Nette\ComponentModel\Component) {
×
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 === ''
×
395
                                || strncmp($this->signalReceiver . '-', $component . '-', strlen($component) + 1) === 0;
×
396

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

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✔
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✔
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
        {
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
                $this->completeTemplate($template);
1✔
485
                $this->sendResponse(new Responses\TextResponse($template));
1✔
486
        }
487

488

489
        /**
490
         * Completes template parameters and file before rendering.
491
         */
492
        protected function completeTemplate(Template $template): void
1✔
493
        {
494
                foreach ($this->getReflection()->getTemplateVariables($this) as $name) {
1✔
495
                        $template->$name ??= $this->$name;
1✔
496
                }
497
                if ($template->getFile() === null) {
1✔
498
                        $template->setFile($this->findTemplateFile());
×
499
                }
500
        }
1✔
501

502

503
        /**
504
         * Finds template file name.
505
         */
506
        public function findTemplateFile(): string
507
        {
508
                $files = $this->formatTemplateFiles();
×
509
                foreach ($files as $file) {
×
510
                        if (is_file($file)) {
×
511
                                return $file;
×
512
                        }
513
                }
514

515
                $file = strtr(Arrays::first($files), '/', DIRECTORY_SEPARATOR);
×
516
                $this->error("Page not found. Missing template '$file'.");
×
517
        }
518

519

520
        /**
521
         * Finds layout template file name.
522
         * @internal
523
         */
524
        public function findLayoutTemplateFile(): ?string
525
        {
526
                if ($this->layout === false) {
×
527
                        return null;
×
528
                }
529

530
                $files = $this->formatLayoutTemplateFiles();
×
531
                foreach ($files as $file) {
×
532
                        if (is_file($file)) {
×
533
                                return $file;
×
534
                        }
535
                }
536

537
                if ($this->layout) {
×
538
                        $file = strtr(Arrays::first($files), '/', DIRECTORY_SEPARATOR);
×
539
                        throw new Nette\FileNotFoundException("Layout not found. Missing template '$file'.");
×
540
                }
541

542
                return null;
×
543
        }
544

545

546
        /**
547
         * Formats layout template file names.
548
         * @return string[]
549
         */
550
        public function formatLayoutTemplateFiles(): array
551
        {
552
                if (preg_match('#/|\\\#', (string) $this->layout)) {
1✔
553
                        return [$this->layout];
1✔
554
                }
555

556
                $layout = $this->layout ?: 'layout';
1✔
557
                $dir = dirname(static::getReflection()->getFileName());
1✔
558
                $levels = substr_count($this->getName(), ':');
1✔
559
                if (!is_dir("$dir/templates")) {
1✔
560
                        $dir = dirname($origDir = $dir);
1✔
561
                        if (!is_dir("$dir/templates")) {
1✔
562
                                $list = ["$origDir/@$layout.latte"];
1✔
563
                                do {
564
                                        $list[] = "$dir/@$layout.latte";
1✔
565
                                } while ($levels-- && ($dir = dirname($dir)));
1✔
566
                                return $list;
1✔
567
                        }
568
                }
569

570
                [, $presenter] = Helpers::splitName($this->getName());
1✔
571
                $list = [
1✔
572
                        "$dir/templates/$presenter/@$layout.latte",
1✔
573
                        "$dir/templates/$presenter.@$layout.latte",
1✔
574
                ];
575
                do {
576
                        $list[] = "$dir/templates/@$layout.latte";
1✔
577
                } while ($levels-- && ($dir = dirname($dir)));
1✔
578

579
                return $list;
1✔
580
        }
581

582

583
        /**
584
         * Formats view template file names.
585
         * @return string[]
586
         */
587
        public function formatTemplateFiles(): array
588
        {
589
                $dir = dirname(static::getReflection()->getFileName());
1✔
590
                if (!is_dir("$dir/templates")) {
1✔
591
                        $dir = dirname($origDir = $dir);
1✔
592
                        if (!is_dir("$dir/templates")) {
1✔
593
                                return [
594
                                        "$origDir/$this->view.latte",
1✔
595
                                ];
596
                        }
597
                }
598

599
                [, $presenter] = Helpers::splitName($this->getName());
1✔
600
                return [
601
                        "$dir/templates/$presenter/$this->view.latte",
1✔
602
                        "$dir/templates/$presenter.$this->view.latte",
1✔
603
                ];
604
        }
605

606

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

615

616
        /**
617
         * Formats render view method name.
618
         */
619
        public static function formatRenderMethod(string $view): string
1✔
620
        {
621
                return 'render' . ucfirst($view);
1✔
622
        }
623

624

625
        protected function createTemplate(?string $class = null): Template
1✔
626
        {
627
                $class ??= $this->formatTemplateClass();
1✔
628
                return $this->getTemplateFactory()->createTemplate($this, $class);
1✔
629
        }
630

631

632
        public function formatTemplateClass(): ?string
633
        {
634
                $base = preg_replace('#Presenter$#', '', static::class);
1✔
635
                return $this->checkTemplateClass($base . ucfirst($this->action) . 'Template')
1✔
636
                        ?? $this->checkTemplateClass($base . 'Template');
1✔
637
        }
638

639

640
        /********************* partial AJAX rendering ****************d*g**/
641

642

643
        final public function getPayload(): \stdClass
644
        {
645
                return $this->payload ??= new \stdClass;
1✔
646
        }
647

648

649
        /**
650
         * Is AJAX request?
651
         */
652
        public function isAjax(): bool
653
        {
654
                if (!isset($this->ajaxMode)) {
1✔
655
                        $this->ajaxMode = $this->httpRequest->isAjax();
1✔
656
                }
657

658
                return $this->ajaxMode;
1✔
659
        }
660

661

662
        /**
663
         * Sends AJAX payload to the output.
664
         * @throws Nette\Application\AbortException
665
         * @return never
666
         */
667
        public function sendPayload(): void
668
        {
669
                $this->sendResponse(new Responses\JsonResponse($this->getPayload()));
×
670
        }
671

672

673
        /**
674
         * Sends JSON data to the output.
675
         * @throws Nette\Application\AbortException
676
         * @return never
677
         */
678
        public function sendJson(mixed $data): void
679
        {
680
                $this->sendResponse(new Responses\JsonResponse($data));
×
681
        }
682

683

684
        /********************* navigation & flow ****************d*g**/
685

686

687
        /**
688
         * Sends response and terminates presenter.
689
         * @throws Nette\Application\AbortException
690
         * @return never
691
         */
692
        public function sendResponse(Application\Response $response): void
1✔
693
        {
694
                $this->response = $response;
1✔
695
                $this->terminate();
1✔
696
        }
697

698

699
        /**
700
         * Correctly terminates presenter.
701
         * @throws Nette\Application\AbortException
702
         * @return never
703
         */
704
        public function terminate(): void
705
        {
706
                throw new Application\AbortException;
1✔
707
        }
708

709

710
        /**
711
         * Forward to another presenter or action.
712
         * @param  array|mixed  $args
713
         * @throws Nette\Application\AbortException
714
         * @return never
715
         */
716
        public function forward(string|Nette\Application\Request $destination, $args = []): void
1✔
717
        {
718
                if ($destination instanceof Application\Request) {
1✔
719
                        $this->sendResponse(new Responses\ForwardResponse($destination));
×
720
                }
721

722
                $args = func_num_args() < 3 && is_array($args)
1✔
723
                        ? $args
1✔
724
                        : array_slice(func_get_args(), 1);
×
725
                $request = $this->linkGenerator->createRequest($this, $destination, $args, 'forward');
1✔
726
                $this->sendResponse(new Responses\ForwardResponse($request));
1✔
727
        }
728

729

730
        /**
731
         * Redirect to another URL and ends presenter execution.
732
         * @throws Nette\Application\AbortException
733
         * @return never
734
         */
735
        public function redirectUrl(string $url, ?int $httpCode = null): void
1✔
736
        {
737
                if ($this->isAjax()) {
1✔
738
                        $this->getPayload()->redirect = $url;
×
739
                        $this->sendPayload();
×
740

741
                } elseif (!$httpCode) {
1✔
742
                        $httpCode = $this->httpRequest->isMethod('post')
1✔
743
                                ? Http\IResponse::S303_PostGet
1✔
744
                                : Http\IResponse::S302_Found;
1✔
745
                }
746

747
                $this->sendResponse(new Responses\RedirectResponse($url, $httpCode));
1✔
748
        }
749

750

751
        /**
752
         * Returns the last created Request.
753
         * @internal
754
         */
755
        final public function getLastCreatedRequest(): ?Application\Request
756
        {
757
                return $this->linkGenerator->lastRequest;
1✔
758
        }
759

760

761
        /**
762
         * Returns the last created Request flag.
763
         * @internal
764
         */
765
        final public function getLastCreatedRequestFlag(string $flag): bool
1✔
766
        {
767
                return (bool) $this->linkGenerator->lastRequest?->hasFlag($flag);
1✔
768
        }
769

770

771
        /**
772
         * Conditional redirect to canonicalized URI.
773
         * @param  mixed  ...$args
774
         * @throws Nette\Application\AbortException
775
         */
776
        public function canonicalize(?string $destination = null, ...$args): void
1✔
777
        {
778
                $request = $this->request;
1✔
779
                if ($this->isAjax() || (!$request->isMethod('get') && !$request->isMethod('head'))) {
1✔
780
                        return;
1✔
781
                }
782

783
                $args = count($args) === 1 && is_array($args[0] ?? null)
1✔
784
                        ? $args[0]
×
785
                        : $args;
1✔
786
                try {
787
                        $url = $this->linkGenerator->link(
1✔
788
                                $destination ?: $this->action,
1✔
789
                                $args + $this->getGlobalState() + $request->getParameters(),
1✔
790
                                $this,
791
                                'redirectX',
1✔
792
                        );
793
                } catch (InvalidLinkException) {
×
794
                }
795

796
                if (!isset($url) || $this->httpRequest->getUrl()->isEqual($url)) {
1✔
797
                        return;
×
798
                }
799

800
                $code = $request->hasFlag($request::VARYING)
1✔
801
                        ? Http\IResponse::S302_Found
1✔
802
                        : Http\IResponse::S301_MovedPermanently;
1✔
803
                $this->sendResponse(new Responses\RedirectResponse($url, $code));
1✔
804
        }
805

806

807
        /**
808
         * Attempts to cache the sent entity by its last modification date.
809
         * @param  ?string  $etag  strong entity tag validator
810
         * @param  ?string  $expire  like '20 minutes'
811
         * @throws Nette\Application\AbortException
812
         */
813
        public function lastModified(
814
                string|int|\DateTimeInterface|null $lastModified,
815
                ?string $etag = null,
816
                ?string $expire = null,
817
        ): void
818
        {
819
                if ($expire !== null) {
×
820
                        $this->httpResponse->setExpiration($expire);
×
821
                }
822

823
                $helper = new Http\Context($this->httpRequest, $this->httpResponse);
×
824
                if (!$helper->isModified($lastModified, $etag)) {
×
825
                        $this->terminate();
×
826
                }
827
        }
828

829

830
        #[\Deprecated]
831
        protected function createRequest(Component $component, string $destination, array $args, string $mode): ?string
832
        {
833
                trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED);
×
834
                return $this->linkGenerator->link($destination, $args, $component, $mode);
×
835
        }
836

837

838
        #[\Deprecated]
839
        public static function parseDestination(string $destination): array
840
        {
841
                trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED);
×
842
                return LinkGenerator::parseDestination($destination);
×
843
        }
844

845

846
        #[\Deprecated]
847
        protected function requestToUrl(Application\Request $request, ?bool $relative = null): string
848
        {
849
                trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED);
×
850
                return $this->linkGenerator->requestToUrl($request, $relative ?? !$this->absoluteUrls);
×
851
        }
852

853

854
        /**
855
         * Invalid link handler. Descendant can override this method to change default behaviour.
856
         * @throws InvalidLinkException
857
         */
858
        protected function handleInvalidLink(InvalidLinkException $e): string
1✔
859
        {
860
                if ($this->invalidLinkMode & self::InvalidLinkException) {
1✔
861
                        throw $e;
1✔
862
                } elseif ($this->invalidLinkMode & self::InvalidLinkWarning) {
1✔
863
                        trigger_error('Invalid link: ' . $e->getMessage(), E_USER_WARNING);
1✔
864
                }
865

866
                return $this->invalidLinkMode & self::InvalidLinkTextual
1✔
867
                        ? '#error: ' . $e->getMessage()
1✔
868
                        : '#';
1✔
869
        }
870

871

872
        /********************* request serialization ****************d*g**/
873

874

875
        /**
876
         * Stores current request to session.
877
         */
878
        public function storeRequest(string $expiration = '+ 10 minutes'): string
1✔
879
        {
880
                $session = $this->getSession('Nette.Application/requests');
1✔
881
                do {
882
                        $key = Nette\Utils\Random::generate(5);
1✔
883
                } while ($session->get($key));
1✔
884

885
                $session->set($key, [$this->user?->getId(), $this->request]);
1✔
886
                $session->setExpiration($expiration, $key);
1✔
887
                return $key;
1✔
888
        }
889

890

891
        /**
892
         * Restores request from session.
893
         */
894
        public function restoreRequest(string $key): void
1✔
895
        {
896
                $session = $this->getSession('Nette.Application/requests');
1✔
897
                $data = $session->get($key);
1✔
898
                if (!$data || ($data[0] !== null && $data[0] !== $this->getUser()->getId())) {
1✔
899
                        return;
1✔
900
                }
901

902
                $request = clone $data[1];
1✔
903
                $session->remove($key);
1✔
904
                $params = $request->getParameters();
1✔
905
                $params[self::FlashKey] = $this->getFlashKey();
1✔
906
                $request->setParameters($params);
1✔
907
                if ($request->isMethod('POST')) {
1✔
908
                        $request->setFlag(Application\Request::RESTORED, true);
1✔
909
                        $this->sendResponse(new Responses\ForwardResponse($request));
1✔
910
                } else {
911
                        $this->redirectUrl($this->linkGenerator->requestToUrl($request));
×
912
                }
913
        }
914

915

916
        /********************* interface StatePersistent ****************d*g**/
917

918

919
        /**
920
         * Descendant can override this method to return the names of custom persistent components.
921
         * @return string[]
922
         */
923
        public static function getPersistentComponents(): array
924
        {
925
                return [];
1✔
926
        }
927

928

929
        /**
930
         * Saves state information for all subcomponents to $this->globalState.
931
         * @return array<string, mixed>
932
         */
933
        public function getGlobalState(?string $forClass = null): array
1✔
934
        {
935
                $sinces = &$this->globalStateSinces;
1✔
936

937
                if (!isset($this->globalState)) {
1✔
938
                        $state = [];
1✔
939
                        foreach ($this->globalParams as $id => $params) {
1✔
940
                                $prefix = $id . self::NameSeparator;
1✔
941
                                foreach ($params as $key => $val) {
1✔
942
                                        $state[$prefix . $key] = $val;
1✔
943
                                }
944
                        }
945

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

948
                        if ($sinces === null) {
1✔
949
                                $sinces = [];
1✔
950
                                foreach ($this->getReflection()->getPersistentParams() as $name => $meta) {
1✔
951
                                        $sinces[$name] = $meta['since'];
1✔
952
                                }
953
                        }
954

955
                        $persistents = $this->getReflection()->getPersistentComponents();
1✔
956

957
                        foreach ($this->getComponentTree() as $component) {
1✔
958
                                if ($component->getParent() === $this) {
1✔
959
                                        // counts on child-first search
960
                                        $since = $persistents[$component->getName()]['since'] ?? false; // false = nonpersistent
1✔
961
                                }
962

963
                                if (!$component instanceof StatePersistent) {
1✔
964
                                        continue;
×
965
                                }
966

967
                                $prefix = $component->getUniqueId() . self::NameSeparator;
1✔
968
                                $params = [];
1✔
969
                                $component->saveState($params);
1✔
970
                                foreach ($params as $key => $val) {
1✔
971
                                        $state[$prefix . $key] = $val;
1✔
972
                                        $sinces[$prefix . $key] = $since;
1✔
973
                                }
974
                        }
975
                } else {
976
                        $state = $this->globalState;
1✔
977
                }
978

979
                if ($forClass !== null) {
1✔
980
                        $tree = Helpers::getClassesAndTraits($forClass);
1✔
981
                        $since = null;
1✔
982
                        foreach ($state as $key => $foo) {
1✔
983
                                if (!isset($sinces[$key])) {
1✔
984
                                        $x = strpos($key, self::NameSeparator);
1✔
985
                                        $x = $x === false ? $key : substr($key, 0, $x);
1✔
986
                                        $sinces[$key] = $sinces[$x] ?? false;
1✔
987
                                }
988

989
                                if ($since !== $sinces[$key]) {
1✔
990
                                        $since = $sinces[$key];
1✔
991
                                        $ok = $since && isset($tree[$since]);
1✔
992
                                }
993

994
                                if (!$ok) {
1✔
995
                                        unset($state[$key]);
1✔
996
                                }
997
                        }
998
                }
999

1000
                return $state;
1✔
1001
        }
1002

1003

1004
        /**
1005
         * Permanently saves state information for all subcomponents to $this->globalState.
1006
         */
1007
        protected function saveGlobalState(): void
1008
        {
1009
                $this->globalParams = [];
1✔
1010
                $this->globalState = $this->getGlobalState();
1✔
1011
        }
1✔
1012

1013

1014
        /**
1015
         * Initializes $this->globalParams, $this->signal & $this->signalReceiver, $this->action, $this->view. Called by run().
1016
         * @throws Nette\Application\BadRequestException if action name is not valid
1017
         */
1018
        private function initGlobalParameters(): void
1019
        {
1020
                // init $this->globalParams
1021
                $this->globalParams = [];
1✔
1022
                $selfParams = [];
1✔
1023

1024
                $params = $this->request->getParameters();
1✔
1025
                if (($tmp = $this->request->getPost('_' . self::SignalKey)) !== null) {
1✔
1026
                        $params[self::SignalKey] = $tmp;
1✔
1027
                } elseif ($this->isAjax()) {
1✔
1028
                        $params += $this->request->getPost();
1✔
1029
                        if (($tmp = $this->request->getPost(self::SignalKey)) !== null) {
1✔
1030
                                $params[self::SignalKey] = $tmp;
1✔
1031
                        }
1032
                }
1033

1034
                foreach ($params as $key => $value) {
1✔
1035
                        if (!preg_match('#^((?:[a-z0-9_]+-)*)((?!\d+$)[a-z0-9_]+)$#Di', (string) $key, $matches)) {
1✔
1036
                                continue;
×
1037
                        } elseif (!$matches[1]) {
1✔
1038
                                $selfParams[$key] = $value;
1✔
1039
                        } else {
1040
                                $this->globalParams[substr($matches[1], 0, -1)][$matches[2]] = $value;
1✔
1041
                        }
1042
                }
1043

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

1050
                $this->changeAction($action);
1✔
1051
                $this->forwarded = false;
1✔
1052

1053
                // init $this->signalReceiver and key 'signal' in appropriate params array
1054
                $this->signalReceiver = $this->getUniqueId();
1✔
1055
                if (isset($selfParams[self::SignalKey])) {
1✔
1056
                        $param = $selfParams[self::SignalKey];
1✔
1057
                        if (!is_string($param)) {
1✔
1058
                                $this->error('Signal name is not string.');
1✔
1059
                        }
1060

1061
                        $pos = strrpos($param, '-');
1✔
1062
                        if ($pos) {
1✔
1063
                                $this->signalReceiver = substr($param, 0, $pos);
1✔
1064
                                $this->signal = substr($param, $pos + 1);
1✔
1065
                        } else {
1066
                                $this->signalReceiver = $this->getUniqueId();
1✔
1067
                                $this->signal = $param;
1✔
1068
                        }
1069

1070
                        if ($this->signal === '') {
1✔
1071
                                $this->signal = null;
×
1072
                        }
1073
                }
1074

1075
                $this->loadState($selfParams);
1✔
1076
        }
1✔
1077

1078

1079
        /**
1080
         * Pops parameters for specified component.
1081
         * @return array<string, mixed>
1082
         * @internal
1083
         */
1084
        final public function popGlobalParameters(string $id): array
1✔
1085
        {
1086
                $res = $this->globalParams[$id] ?? [];
1✔
1087
                unset($this->globalParams[$id]);
1✔
1088
                return $res;
1✔
1089
        }
1090

1091

1092
        /********************* flash session ****************d*g**/
1093

1094

1095
        private function getFlashKey(): ?string
1096
        {
1097
                $flashKey = $this->getParameter(self::FlashKey);
1✔
1098
                return is_string($flashKey) && $flashKey !== ''
1✔
1099
                        ? $flashKey
1✔
1100
                        : null;
1✔
1101
        }
1102

1103

1104
        /**
1105
         * Checks if a flash session namespace exists.
1106
         */
1107
        public function hasFlashSession(): bool
1108
        {
1109
                $flashKey = $this->getFlashKey();
1✔
1110
                return $flashKey !== null
1✔
1111
                        && $this->getSession()->hasSection('Nette.Application.Flash/' . $flashKey);
1✔
1112
        }
1113

1114

1115
        /**
1116
         * Returns session namespace provided to pass temporary data between redirects.
1117
         */
1118
        public function getFlashSession(): Http\SessionSection
1119
        {
1120
                $flashKey = $this->getFlashKey();
1✔
1121
                if ($flashKey === null) {
1✔
1122
                        $this->params[self::FlashKey] = $flashKey = Nette\Utils\Random::generate(4);
1✔
1123
                }
1124

1125
                return $this->getSession('Nette.Application.Flash/' . $flashKey);
1✔
1126
        }
1127

1128

1129
        /********************* services ****************d*g**/
1130

1131

1132
        final public function injectPrimary(
1✔
1133
                Http\IRequest $httpRequest,
1134
                Http\IResponse $httpResponse,
1135
                ?Application\IPresenterFactory $presenterFactory = null,
1136
                ?Nette\Routing\Router $router = null,
1137
                ?Http\Session $session = null,
1138
                ?Nette\Security\User $user = null,
1139
                ?TemplateFactory $templateFactory = null,
1140
        ): void
1141
        {
1142
                $this->httpRequest = $httpRequest;
1✔
1143
                $this->httpResponse = $httpResponse;
1✔
1144
                $this->session = $session;
1✔
1145
                $this->user = $user;
1✔
1146
                $this->templateFactory = $templateFactory;
1✔
1147
                if ($router && $presenterFactory) {
1✔
1148
                        $url = $httpRequest->getUrl();
1✔
1149
                        $this->linkGenerator = new LinkGenerator(
1✔
1150
                                $router,
1✔
1151
                                new Http\UrlScript($url->getHostUrl() . $url->getScriptPath()),
1✔
1152
                                $presenterFactory,
1153
                        );
1154
                }
1155
        }
1✔
1156

1157

1158
        final public function getHttpRequest(): Http\IRequest
1159
        {
1160
                return $this->httpRequest;
1✔
1161
        }
1162

1163

1164
        final public function getHttpResponse(): Http\IResponse
1165
        {
1166
                return $this->httpResponse;
1✔
1167
        }
1168

1169

1170
        final public function getSession(?string $namespace = null): Http\Session|Http\SessionSection
1✔
1171
        {
1172
                if (empty($this->session)) {
1✔
1173
                        throw new Nette\InvalidStateException('Service Session has not been set.');
×
1174
                }
1175

1176
                return $namespace === null
1✔
1177
                        ? $this->session
1✔
1178
                        : $this->session->getSection($namespace);
1✔
1179
        }
1180

1181

1182
        final public function getUser(): Nette\Security\User
1183
        {
1184
                return $this->user ?? throw new Nette\InvalidStateException('Service User has not been set.');
1✔
1185
        }
1186

1187

1188
        final public function getTemplateFactory(): TemplateFactory
1189
        {
1190
                return $this->templateFactory ?? throw new Nette\InvalidStateException('Service TemplateFactory has not been set.');
1✔
1191
        }
1192

1193

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