Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
9d6183f
added support for backed enums in parameters [Closes nette/nette#1558]
dg Jul 27, 2026
a2a6c6a
removed withoutUserInfo() calls, nette/http no longer passes user inf…
dg Aug 5, 2026
0bd2133
Application: initial request with HTTP method FORWARD is rejected, it…
dg Aug 5, 2026
627ec3e
added AGENTS.md & DOCS
dg Aug 13, 2026
f1d5818
added upgrading.md
dg Aug 24, 2026
22928c2
fixed PHPStan errors
dg Aug 26, 2026
82f5bf8
Application: fixes the error-handling flow in run()
dg Sep 7, 2026
d75e43f
MicroPresenter: fixes uninitialized $request and a missing container …
dg Sep 7, 2026
fbc52a3
Routers\RouteList: constructUrl() exempts Nette: presenters from the …
dg Sep 7, 2026
cb70e48
PresenterFactoryCallback: fixes an endless refresh loop on ambiguous …
dg Sep 7, 2026
e45b329
tests: the robotLoader discovery test really tests its own container
dg Sep 7, 2026
2eb3e82
FileResponse: sanitizes filename to prevent Content-Disposition injec…
dg Sep 16, 2026
b9cb17b
FileResponse: fixes Content-Range handling per RFC 9110
dg Sep 16, 2026
dcbc7b2
AccessPolicy: resolves presenter lazily
dg Sep 22, 2026
98dda1e
Presenter: #[Requires(methods:)] on an action or render method can wi…
dg Sep 22, 2026
6150e1a
cs
dg Oct 28, 2026
be22258
Request: constants are PascalCase
dg Oct 13, 2026
51d252a
uses dresscode
dg Oct 14, 2026
95da0aa
added support for PHP 8.6
dg Oct 14, 2026
7db5953
ControlNode: deprecate missing comma before arguments
dg Jul 21, 2026
249dbe1
Presenter: detects an infinite loop of switch() calls
dg Jul 12, 2026
20e0725
ComponentReflection: getActionRenderMethod() caches negative results too
dg Oct 2, 2026
11d11c3
UIExtension: findNonce() accepts a base64url nonce
dg Oct 11, 2026
a02bab4
UIExtension: adds getCacheKey() depending on the snippet attribute
dg Oct 11, 2026
0127807
RoutingPanel: fixes lost {else} branch and missing space in tab
dg Oct 19, 2026
f59fd93
LinkGenerator: isLinkCurrent() does not throw on targets with #[Requi…
dg Oct 28, 2026
a8ff722
TemplateFactory: removed redundant class_exists()
dg Oct 28, 2026
5193e20
Component: missing #[Parameter] without default value causes 4xx, a n…
dg Oct 28, 2026
b8a9ede
ApplicationExtension: warns when a presenter overrides createRequest(…
dg Oct 28, 2026
0fea213
RoutingPanel: status colors without !important, so they can be overri…
dg Oct 28, 2026
d8891c3
Presenter: layout path detection uses strpbrk() instead of a regexp
dg Oct 28, 2026
2d0b446
tests: LinkGenerator with absolute destination
dg Oct 28, 2026
9dfbfed
UI\Form: compatible with both nette/forms 3.x and the upcoming 4.0
dg Jul 2, 2026
e819ce3
UI\Form: adapted to the SubmissionSource model of nette/forms 4.0
dg Jul 2, 2026
3037a57
opened 4.0-dev
dg Dec 14, 2023
b8ee2f9
composer: increased dependencies versions
dg Jan 21, 2026
36270c5
added type hints (BC break)
dg Feb 8, 2024
c2b4beb
Presenter::handleInvalidLink() -> processInvalidLink() (BC break)
dg May 12, 2024
57d3049
removed legacy services names (BC break)
dg Oct 27, 2026
d583f89
Revert "UI\PresenterComponent: removed references created by loadStat…
dg Oct 24, 2022
2cf0089
Component: method checkRequirements() is called for createComponent<N…
dg Apr 20, 2024
e47300e
Component: only UI components can be added to presenter/component (BC…
dg Apr 7, 2024
b1a58bc
Presenter: removed constructor (BC break!)
dg Dec 11, 2023
385455c
removed support for @annotations (BC break)
dg Nov 29, 2025
dd976e2
RoutingPanel: added support for AI agents
dg Apr 22, 2026
04f1ab2
uses nette/routing 4.0
dg Jan 21, 2024
71cc17a
removed {templatePrint}
dg Apr 19, 2024
f53d521
Presenter::argsToParams() now distinguish between NULL and ''
dg May 13, 2024
2f45d4b
AccessPolicy: class-level #[Requires] is inherited from ancestors
dg Oct 21, 2026
beeefad
Control: flashMessage() does not create the template
dg Oct 26, 2026
faf1f70
Component: StatePersistent component that is not UI\Component cannot …
dg Oct 28, 2026
7012206
PresenterFactory: mapping accepts submodules like 'Admin:Forum', the …
dg Oct 28, 2026
b7068cb
fix: Template::render() accepts object for template parameters (#351)
jakduch Mar 5, 2026
93be5c5
fixed object template parameter tests
jakduch Sep 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
.gitattributes export-ignore
.github/ export-ignore
.gitignore export-ignore
ncs.* export-ignore
AGENTS.md export-ignore
dresscode.neon export-ignore
phpstan*.neon export-ignore
src/**/*.latte export-ignore
docs/ export-ignore
tests/ export-ignore

* text=auto
*.php* diff=php
*.sh text eol=lf
24 changes: 12 additions & 12 deletions .github/workflows/coding-style.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,29 @@ name: Coding Style
on: [push, pull_request]

jobs:
nette_cc:
name: Nette Code Checker
dresscode:
name: DressCode
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: shivammathur/setup-php@v2
with:
php-version: 8.3
php-version: 8.4
coverage: none

- run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress
- run: php temp/code-checker/code-checker --strict-types --no-progress --ignore expected --ignore "src/Bridges/ApplicationTracy/*.latte"
- run: composer create-project dresscode/dresscode temp/dresscode ^1.1 --no-progress
- run: php temp/dresscode/bin/dresscode check


nette_cs:
name: Nette Coding Standard
nette_cc:
name: Nette Code Checker
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: shivammathur/setup-php@v2
with:
php-version: 8.3
php-version: 8.4
coverage: none

- run: composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress
- run: php temp/coding-standard/ecs check
- run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress
- run: php temp/code-checker/code-checker --strict-types --no-progress --ignore expected --ignore "src/Bridges/ApplicationTracy/*.latte"
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['8.3', '8.4', '8.5']
php: ['8.3', '8.4', '8.5', '8.6']

fail-fast: false

Expand Down
67 changes: 67 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# To My Agents!

It is my fervent wish that this file guide every AI coding agent working with code in this repository.

## Documentation

Any distilled, agent-facing documentation for this package - how it works
internally and the rationale behind key design decisions - lives in `docs/`.
Consult it before non-trivial changes; it is the source of truth from which the
public manual is distilled.

The presenter lifecycle, link generation, persistent state, routing, and template
lookup carry subtle non-local invariants - read `docs/internals` before touching
them.

`docs/upgrading.md` records, per released version, every change visible to
users of the package (renames, removals, deprecations, signatures, behavior,
configuration), each with its commit hash; internal changes are not in it. It
is the place to look up the history of a public symbol. Do not write to it; it
is extended at release time from the git history.

## Project Overview

Nette Application is a full-stack, component-based MVC framework library for PHP -
the core application layer of the Nette Framework: presenters, components, routing,
templating integration, and the request/response cycle. It is a library package,
not a runnable application, and integrates with the rest of the framework through
`src/Bridges` (DI, Latte, Tracy).

- **PHP Version**: 8.3 - 8.5
- **Package**: `nette/application`

## Essential Commands

```bash
# Run all tests
composer tester # or: vendor/bin/tester tests -s

# Run one test directory / file
vendor/bin/tester tests/UI -s
php tests/Application/Presenter.twoDomains.phpt

# Static analysis (PHPStan level 8)
composer phpstan
```

## Conventions

- Every file starts with `declare(strict_types=1);`; tabs for indentation; Nette
Coding Standard (PSR-12 based) with the opening brace on its own line.
- Tests are Nette Tester `.phpt` files mirroring `src/` under `tests/`; use
`test()` / `testException()` and `Assert::*`. Temp files go to `tests/tmp/{pid}/`,
and `tests/types/` holds PHPStan type-inference fixtures.

## Working in this repo

- **The presenter lifecycle is exact and load-bearing.** The precise phase order
(action, canonicalization, signal dispatch, `beforeRender`, `render`, the
`onRender` hook, `afterRender`), `AbortException`-based termination, and the
4xx-not-500 invalid-link contract are documented in `docs/internals/lifecycle.md`.
Don't reconstruct them from memory.
- **Links and persistent state span the whole component tree.** Link-generation
modes and whole-tree global-state aggregation are the trickiest code here; see
`docs/internals/link-generation.md` and `persistent-state.md`.
- User-facing how-to (routing DSL, signals, snippets, component factories, flash
messages, DI/NEON configuration) is manual material and lives in the public web
docs, not here.
14 changes: 7 additions & 7 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
}
],
"require": {
"php": "8.3 - 8.5",
"nette/component-model": "^3.2 || ^4.0",
"php": "8.3 - 8.6",
"nette/component-model": "^4.0",
"nette/http": "^3.4",
"nette/routing": "^3.1.1",
"nette/routing": "^3.1.5",
"nette/utils": "^4.1"
},
"suggest": {
Expand All @@ -25,7 +25,7 @@
"nette/tester": "^2.6",
"nette/caching": "^3.2",
"nette/di": "^3.2",
"nette/forms": "^3.2",
"nette/forms": "^4.0",
"nette/robot-loader": "^4.0",
"nette/security": "^3.2",
"latte/latte": "^3.1.4",
Expand All @@ -39,9 +39,9 @@
"conflict": {
"nette/caching": "<3.2",
"nette/di": "<3.2",
"nette/forms": "<3.2",
"nette/forms": "<4.0",
"nette/schema": "<1.3",
"latte/latte": "<3.1.4 || >=3.2",
"latte/latte": "<3.1.5 || >=3.2",
"tracy/tracy": "<2.12"
},
"autoload": {
Expand All @@ -57,7 +57,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "3.3-dev"
"dev-master": "4.0-dev"
}
},
"config": {
Expand Down
81 changes: 81 additions & 0 deletions docs/decisions/2025-02-27 linkbase-tag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# {linkBase} tag - compile-time base for relative links in a template

Status: accepted · 2025-02-27 · branch `v3.2` (released in 3.2.7)

## Context

A flexible directory structure is one of the most valuable features of Nette
Application, but it creates a problem with shared layouts: relative link targets
(`Dashboard:`, `Products:Overview:`) are resolved against the *current*
presenter. When the same layout is used by both `Admin:Dashboard` and
`Admin:Products:Detail`, the target `Dashboard:` resolves correctly to
`Admin:Dashboard:default` in one case and to the non-existent
`Admin:Products:Dashboard:default` in the other.

The previous workaround was to write every target in layouts absolutely
(`:Admin:Dashboard:`). It works, but the module prefix is duplicated in every
link, so moving or renaming a module means editing templates en masse.

## Considered options

- **Status quo (absolute links everywhere)** - no new concept, but prefix
duplication and fragility when refactoring modules.
- **Runtime solution** (setting the base on the presenter/component, or a
template variable threaded into `LinkGenerator`) - collides with the shared
template cache: the same compiled layout is used by presenters from different
modules, so the base would have to become part of the cache key, or a runtime
cost would be paid on every link. It would also make the template no longer
self-describing - a link's target would depend on state set somewhere in PHP.
- **Compile-time tag in the template** (chosen) - the base is declared directly
in the template it concerns, and expansion happens at compile time.

## Decision

A new tag `{linkBase module}` plus a compile-time pass `applyLinkBasePass`
registered in `UIExtension::getPasses()`:

- The tag must appear in the template head (`Tag::isInHead()`, otherwise
`CompileException`) and applies to the **whole template file** - not to
included files, which are compiled separately and may carry their own
`{linkBase}`. It emits no code itself (`print()` returns `''`); all the work
is done by the pass. Only the first tag found is used
(`NodeHelpers::findFirst`).
- The pass traverses the AST and rewrites **all** `LinkNode`s - i.e. `{link}`,
`{plink}` and `n:href` uniformly.
- When both the target and the base are static strings, the destination is
rewritten directly at compile time (`LinkGenerator::applyBase()` called from
the pass) - **zero runtime overhead**. When either is an expression, the
destination is wrapped in a runtime call `LinkGenerator::applyBase(%node,
%node)` via `AuxiliaryNode`.
- Semantics of `LinkGenerator::applyBase($link, $base)`: the prefix `:$base:` is
added only when the target **contains a colon and does not start with one**.
Thus the base applies exclusively to relative targets that name a presenter
(`Users:list` → `:Admin:Users:list`); untouched are absolute targets
(`:Foo:`), targets within the current presenter (`show`, `this`), signals
(`click!`) and aliases - none of which have a colon in the middle.

## Consequences

- (+) Layouts and templates shared by presenters at different module depths link
consistently, regardless of who renders them.
- (+) Renaming or moving a module is a single change in `{linkBase}` instead of
editing every link.
- (+) For static links, expansion happens at compile time; nothing is paid at
runtime.
- (−) Another tag and concept to learn; the per-template-file scope may surprise
(includes do not inherit the base).
- (−) The base cannot be set per presenter/module from PHP - that remains open.

## Code

- `src/Bridges/ApplicationLatte/Nodes/LinkBaseNode.php` - the tag and the
`applyLinkBasePass` pass
- `src/Application/LinkGenerator.php` - `applyBase()` (`@internal`)
- tag and pass registration in `src/Bridges/ApplicationLatte/UIExtension.php`
(`getTags()`, `getPasses()`)
- test: `tests/Bridges.Latte/{linkBase}.phpt` (static and dynamic targets)
- commit `e3beab847`, released in `v3.2.7`

The `|absoluteUrl` filter shipped in the same release for forcing an absolute URL
on an arbitrary value (image path, database value), complementing this tag for
cases outside presenter link targets.
65 changes: 65 additions & 0 deletions docs/internals/access-control.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Access control & CSRF

`AccessPolicy` (internal) evaluates `#[Requires]` attributes. The attribute is
repeatable, class- or method-level, and matched with `IS_INSTANCEOF`, so subclasses
(like the deprecated `#[CrossOrigin]`) count as `Requires` instances.

Class-level attributes are **inherited**, unlike PHP attributes in general:
`AccessPolicy` collects them along the whole ancestor chain, so a rule on a base
presenter applies to every descendant. A rule (a property of `Requires`) set
explicitly in a descendant overrides the same rule of all its ancestors; the
other rules keep combining. Method-level attributes follow plain PHP semantics.

## Enforcement runs at three sites

No single file shows this; the policy is applied wherever user code is about to run:

1. **Class-level** — `Presenter::run()`, in the checkRequirements phase, on the
presenter's own reflection (before `startup`).
2. **Method-level** — `Component::tryCall()`, i.e. on every `action*`, `render*`
and `handle*` invocation, right before argument conversion.
3. **Factory methods** — `Component::createComponent()` checks the policy on the
`createComponent<Name>()` method, so a component can be guarded at creation.

At all three sites the (older, unrelated) `checkRequirements()` user hook is
also called with the same reflection — the two mechanisms coexist.

## `#[Requires]` semantics

- `methods:` — allowed HTTP methods; `['*']` is a wildcard. **Trap:** a
*class-level* `methods:` empties `$presenter->allowedMethods` to bypass the
deprecated legacy `Presenter::checkHttpMethod()`, so the attribute fully replaces
it. The legacy check runs before the method-level attributes, so it skips itself
when the `action*` or `render*` method of the current action carries `methods:`.
Violation → 405 with an `Allow` header.
- `actions:` — method-level use is allowed **only in presenters**
(`LogicException` otherwise). Violation → 4xx via `error()`.
- `forward: true` — the element is reachable only via forward; it also makes the
element **non-linkable**: `AccessPolicy::isLinkable()` is what
`LinkGenerator::validateLinkTarget` consults for every mode except `forward`
(see link-generation.md). Violation at runtime → 4xx.
- `ajax: true` — non-AJAX request → 403.
- `sameOrigin: true` — see below.

## The implicit signal CSRF rule

**Every `handle*` method implicitly gets `Requires(sameOrigin: true)`** — added by
`AccessPolicy::applyInternalRules` — unless the method opts out via
`#[Requires(sameOrigin: false)]`, the deprecated `#[CrossOrigin]`, or a legacy
`@crossOrigin` annotation. Same-origin is verified from the **`Sec-Fetch-Site`
header** (`$httpRequest->isFrom(FetchSite::SameOrigin)`), not from a SameSite
cookie.

A violation does **not** produce an error: it calls `Presenter::detectedCsrf()`,
whose default is `redirect('this')` — **the signal is silently dropped** and the
page reloads without it. `detectedCsrf()` is public and overridable.

## `UI\Form` has its own, parallel check

`Form::signalReceived('submit')` does **not** go through `AccessPolicy` for CSRF:
it re-checks `Sec-Fetch-Site` itself, gated by the `$crossOrigin` flag from
nette/forms (`allowCrossOrigin()`), and calls the same `detectedCsrf()`. Two more
suppressions live here: a request with the `RESTORED` flag (set by
`Presenter::restoreRequest` for re-sent POSTs) skips `fireEvents()` entirely, and
`receiveHttpData()` returns null for forwarded requests — a form never submits
across a forward.
Loading