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

nette / application / 19834954118

01 Dec 2025 07:31PM UTC coverage: 82.856% (-0.1%) from 82.997%
19834954118

push

github

dg
TemplateGenerator WIP

5 of 9 new or added lines in 2 files covered. (55.56%)

34 existing lines in 2 files now uncovered.

1938 of 2339 relevant lines covered (82.86%)

0.83 hits per line

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

75.19
/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
        private array $globalParams = [];
100
        private array $globalState;
101
        private ?array $globalStateSinces;
102
        private string $action = '';
103
        private string $view = '';
104
        private bool $forwarded = false;
105
        private string|bool $layout = '';
106
        private \stdClass $payload;
107
        private string $signalReceiver;
108
        private ?string $signal = null;
109
        private bool $ajaxMode;
110
        private bool $startupCheck = false;
111
        private readonly Nette\Http\IRequest $httpRequest;
112
        private readonly Nette\Http\IResponse $httpResponse;
113
        private readonly ?Nette\Http\Session $session;
114
        private readonly ?Nette\Security\User $user;
115
        private readonly ?TemplateFactory $templateFactory;
116
        private readonly LinkGenerator $linkGenerator;
117

118

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

123

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

129

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

138

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

144

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

151

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

160

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

167

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

173

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

176

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

279

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

288

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

297

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

305

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

310

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

323

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

335

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

338

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

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

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

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

362

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

371

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

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

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

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

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

400

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

403

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

414

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

424

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

433

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

442

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

453

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

462

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

472

473
        /**
474
         * @throws Nette\Application\AbortException
475
         * @return never
476
         */
477
        public function sendTemplate(?Template $template = null): void
1✔
478
        {
479
                $template ??= $this->getTemplate();
1✔
480
                $this->completeTemplate($template);
1✔
481
                $this->sendResponse(new Responses\TextResponse($template));
1✔
482
        }
483

484

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

498

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

511
                $file = strtr(Arrays::first($files), '/', DIRECTORY_SEPARATOR);
×
512
                $this->error("Page not found. Missing template '$file'.");
×
513
        }
514

515

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

526
                $files = $this->formatLayoutTemplateFiles();
×
527
                foreach ($files as $file) {
×
528
                        if (is_file($file)) {
×
529
                                return $file;
×
530
                        }
531
                }
532

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

538
                return null;
×
539
        }
540

541

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

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

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

575
                return $list;
1✔
576
        }
577

578

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

595
                [, $presenter] = Helpers::splitName($this->getName());
1✔
596
                return [
597
                        "$dir/templates/$presenter/$this->view.latte",
1✔
598
                        "$dir/templates/$presenter.$this->view.latte",
1✔
599
                ];
600
        }
601

602

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

611

612
        /**
613
         * Formats render view method name.
614
         */
615
        public static function formatRenderMethod(string $view): string
1✔
616
        {
617
                return 'render' . ucfirst($view);
1✔
618
        }
619

620

621
        protected function createTemplate(?string $class = null): Template
1✔
622
        {
623
                $class ??= $this->formatTemplateClass();
1✔
624
                return $this->getTemplateFactory()->createTemplate($this, $class);
1✔
625
        }
626

627

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

635

636
        /********************* partial AJAX rendering ****************d*g**/
637

638

639
        final public function getPayload(): \stdClass
640
        {
641
                return $this->payload ??= new \stdClass;
1✔
642
        }
643

644

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

654
                return $this->ajaxMode;
1✔
655
        }
656

657

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

668

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

679

680
        /********************* navigation & flow ****************d*g**/
681

682

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

694

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

705

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

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

725

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

737
                } elseif (!$httpCode) {
1✔
738
                        $httpCode = $this->httpRequest->isMethod('post')
1✔
739
                                ? Http\IResponse::S303_PostGet
×
740
                                : Http\IResponse::S302_Found;
1✔
741
                }
742

743
                $this->sendResponse(new Responses\RedirectResponse($url, $httpCode));
1✔
744
        }
745

746

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

756

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

766

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

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

792
                if (!isset($url) || $this->httpRequest->getUrl()->isEqual($url)) {
1✔
793
                        return;
×
794
                }
795

796
                $code = $request->hasFlag($request::VARYING)
1✔
797
                        ? Http\IResponse::S302_Found
×
798
                        : Http\IResponse::S301_MovedPermanently;
1✔
799
                $this->sendResponse(new Responses\RedirectResponse($url, $code));
1✔
800
        }
801

802

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

819
                $helper = new Http\Context($this->httpRequest, $this->httpResponse);
×
820
                if (!$helper->isModified($lastModified, $etag)) {
×
821
                        $this->terminate();
×
822
                }
823
        }
824

825

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

833

834
        #[\Deprecated]
835
        public static function parseDestination(string $destination): array
836
        {
UNCOV
837
                trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED);
×
UNCOV
838
                return LinkGenerator::parseDestination($destination);
×
839
        }
840

841

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

849

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

862
                return $this->invalidLinkMode & self::InvalidLinkTextual
1✔
863
                        ? '#error: ' . $e->getMessage()
1✔
864
                        : '#';
1✔
865
        }
866

867

868
        /********************* request serialization ****************d*g**/
869

870

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

881
                $session->set($key, [$this->user?->getId(), $this->request]);
1✔
882
                $session->setExpiration($expiration, $key);
1✔
883
                return $key;
1✔
884
        }
885

886

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

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

911

912
        /********************* interface StatePersistent ****************d*g**/
913

914

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

924

925
        /**
926
         * Saves state information for all subcomponents to $this->globalState.
927
         */
928
        public function getGlobalState(?string $forClass = null): array
1✔
929
        {
930
                $sinces = &$this->globalStateSinces;
1✔
931

932
                if (!isset($this->globalState)) {
1✔
933
                        $state = [];
1✔
934
                        foreach ($this->globalParams as $id => $params) {
1✔
935
                                $prefix = $id . self::NameSeparator;
1✔
936
                                foreach ($params as $key => $val) {
1✔
937
                                        $state[$prefix . $key] = $val;
1✔
938
                                }
939
                        }
940

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

943
                        if ($sinces === null) {
1✔
944
                                $sinces = [];
1✔
945
                                foreach ($this->getReflection()->getPersistentParams() as $name => $meta) {
1✔
946
                                        $sinces[$name] = $meta['since'];
1✔
947
                                }
948
                        }
949

950
                        $persistents = $this->getReflection()->getPersistentComponents();
1✔
951

952
                        foreach ($this->getComponentTree() as $component) {
1✔
953
                                if ($component->getParent() === $this) {
1✔
954
                                        // counts on child-first search
955
                                        $since = $persistents[$component->getName()]['since'] ?? false; // false = nonpersistent
1✔
956
                                }
957

958
                                if (!$component instanceof StatePersistent) {
1✔
UNCOV
959
                                        continue;
×
960
                                }
961

962
                                $prefix = $component->getUniqueId() . self::NameSeparator;
1✔
963
                                $params = [];
1✔
964
                                $component->saveState($params);
1✔
965
                                foreach ($params as $key => $val) {
1✔
966
                                        $state[$prefix . $key] = $val;
1✔
967
                                        $sinces[$prefix . $key] = $since;
1✔
968
                                }
969
                        }
970
                } else {
971
                        $state = $this->globalState;
1✔
972
                }
973

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

984
                                if ($since !== $sinces[$key]) {
1✔
985
                                        $since = $sinces[$key];
1✔
986
                                        $ok = $since && isset($tree[$since]);
1✔
987
                                }
988

989
                                if (!$ok) {
1✔
990
                                        unset($state[$key]);
1✔
991
                                }
992
                        }
993
                }
994

995
                return $state;
1✔
996
        }
997

998

999
        /**
1000
         * Permanently saves state information for all subcomponents to $this->globalState.
1001
         */
1002
        protected function saveGlobalState(): void
1003
        {
1004
                $this->globalParams = [];
1✔
1005
                $this->globalState = $this->getGlobalState();
1✔
1006
        }
1✔
1007

1008

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

1019
                $params = $this->request->getParameters();
1✔
1020
                if (($tmp = $this->request->getPost('_' . self::SignalKey)) !== null) {
1✔
1021
                        $params[self::SignalKey] = $tmp;
1✔
1022
                } elseif ($this->isAjax()) {
1✔
1023
                        $params += $this->request->getPost();
1✔
1024
                        if (($tmp = $this->request->getPost(self::SignalKey)) !== null) {
1✔
1025
                                $params[self::SignalKey] = $tmp;
1✔
1026
                        }
1027
                }
1028

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

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

1045
                $this->changeAction($action);
1✔
1046
                $this->forwarded = false;
1✔
1047

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

1056
                        $pos = strrpos($param, '-');
1✔
1057
                        if ($pos) {
1✔
1058
                                $this->signalReceiver = substr($param, 0, $pos);
1✔
1059
                                $this->signal = substr($param, $pos + 1);
1✔
1060
                        } else {
1061
                                $this->signalReceiver = $this->getUniqueId();
1✔
1062
                                $this->signal = $param;
1✔
1063
                        }
1064

1065
                        if ($this->signal === '') {
1✔
UNCOV
1066
                                $this->signal = null;
×
1067
                        }
1068
                }
1069

1070
                $this->loadState($selfParams);
1✔
1071
        }
1✔
1072

1073

1074
        /**
1075
         * Pops parameters for specified component.
1076
         * @internal
1077
         */
1078
        final public function popGlobalParameters(string $id): array
1✔
1079
        {
1080
                $res = $this->globalParams[$id] ?? [];
1✔
1081
                unset($this->globalParams[$id]);
1✔
1082
                return $res;
1✔
1083
        }
1084

1085

1086
        /********************* flash session ****************d*g**/
1087

1088

1089
        private function getFlashKey(): ?string
1090
        {
1091
                $flashKey = $this->getParameter(self::FlashKey);
1✔
1092
                return is_string($flashKey) && $flashKey !== ''
1✔
UNCOV
1093
                        ? $flashKey
×
1094
                        : null;
1✔
1095
        }
1096

1097

1098
        /**
1099
         * Checks if a flash session namespace exists.
1100
         */
1101
        public function hasFlashSession(): bool
1102
        {
1103
                $flashKey = $this->getFlashKey();
1✔
1104
                return $flashKey !== null
1✔
1105
                        && $this->getSession()->hasSection('Nette.Application.Flash/' . $flashKey);
1✔
1106
        }
1107

1108

1109
        /**
1110
         * Returns session namespace provided to pass temporary data between redirects.
1111
         */
1112
        public function getFlashSession(): Http\SessionSection
1113
        {
UNCOV
1114
                $flashKey = $this->getFlashKey();
×
UNCOV
1115
                if ($flashKey === null) {
×
1116
                        $this->params[self::FlashKey] = $flashKey = Nette\Utils\Random::generate(4);
×
1117
                }
1118

UNCOV
1119
                return $this->getSession('Nette.Application.Flash/' . $flashKey);
×
1120
        }
1121

1122

1123
        /********************* services ****************d*g**/
1124

1125

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

1151

1152
        final public function getHttpRequest(): Http\IRequest
1153
        {
1154
                return $this->httpRequest;
1✔
1155
        }
1156

1157

1158
        final public function getHttpResponse(): Http\IResponse
1159
        {
1160
                return $this->httpResponse;
1✔
1161
        }
1162

1163

1164
        final public function getSession(?string $namespace = null): Http\Session|Http\SessionSection
1✔
1165
        {
1166
                if (empty($this->session)) {
1✔
UNCOV
1167
                        throw new Nette\InvalidStateException('Service Session has not been set.');
×
1168
                }
1169

1170
                return $namespace === null
1✔
UNCOV
1171
                        ? $this->session
×
1172
                        : $this->session->getSection($namespace);
1✔
1173
        }
1174

1175

1176
        final public function getUser(): Nette\Security\User
1177
        {
UNCOV
1178
                return $this->user ?? throw new Nette\InvalidStateException('Service User has not been set.');
×
1179
        }
1180

1181

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

1187

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