diff --git a/ai/cs/@home.texy b/ai/cs/@home.texy new file mode 100644 index 0000000000..e2385cdf80 --- /dev/null +++ b/ai/cs/@home.texy @@ -0,0 +1,11 @@ +Nette AI +******** + +- [Introduction |guide] +- [Getting Started |getting-started] +- [MCP Inspector |mcp-inspector] +- [Claude Code |claude-code] +- [Tips & Best Practices |tips] + +{{maintitle: Nette AI – Vibe Coding with Nette Framework}} +{{description: Build Nette applications with AI assistance. MCP Inspector gives any AI tool deep knowledge of your application's DI container, database, routing, and errors. No hallucinations, just clean code.}} diff --git a/ai/cs/@meta.texy b/ai/cs/@meta.texy new file mode 100644 index 0000000000..e06cc9886c --- /dev/null +++ b/ai/cs/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette AI}} diff --git a/ai/en/@home.texy b/ai/en/@home.texy new file mode 100644 index 0000000000..e2385cdf80 --- /dev/null +++ b/ai/en/@home.texy @@ -0,0 +1,11 @@ +Nette AI +******** + +- [Introduction |guide] +- [Getting Started |getting-started] +- [MCP Inspector |mcp-inspector] +- [Claude Code |claude-code] +- [Tips & Best Practices |tips] + +{{maintitle: Nette AI – Vibe Coding with Nette Framework}} +{{description: Build Nette applications with AI assistance. MCP Inspector gives any AI tool deep knowledge of your application's DI container, database, routing, and errors. No hallucinations, just clean code.}} diff --git a/ai/en/@left-menu.texy b/ai/en/@left-menu.texy new file mode 100644 index 0000000000..54132f474a --- /dev/null +++ b/ai/en/@left-menu.texy @@ -0,0 +1,5 @@ +- [Introduction |guide] +- [Getting Started |getting-started] +- [MCP Inspector |mcp-inspector] +- [Claude Code |claude-code] +- [Tips & Best Practices |tips] diff --git a/ai/en/@meta.texy b/ai/en/@meta.texy new file mode 100644 index 0000000000..e06cc9886c --- /dev/null +++ b/ai/en/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette AI}} diff --git a/ai/en/claude-code.texy b/ai/en/claude-code.texy new file mode 100644 index 0000000000..6d9e63e82e --- /dev/null +++ b/ai/en/claude-code.texy @@ -0,0 +1,269 @@ +Claude Code Plugin +****************** + +
"decorator .[prism-token prism-atrule]":[#Decorator]: "Декоратор .[prism-token prism-comment]"
diff --git a/dependency-injection/cs/configuration.texy b/dependency-injection/cs/configuration.texy index d89e0ab829..07c89b97b0 100644 --- a/dependency-injection/cs/configuration.texy +++ b/dependency-injection/cs/configuration.texy @@ -8,7 +8,7 @@ Přehled konfiguračních voleb pro Nette DI kontejner. Konfigurační soubor =================== -Nette DI kontejner se snadno ovládá pomocí konfiguračních souborů. Ty se obvykle zapisují ve [formátu NEON|neon:format]. K editaci doporučujeme [editory s podporou |best-practices:editors-and-tools#IDE editor] tohoto formátu. +Nette DI kontejner se snadno ovládá pomocí konfiguračních souborů. Ty se obvykle zapisují ve [formátu NEON|neon:format]. K editaci doporučujeme [editory s podporou |tools:ide] tohoto formátu."decorator .[prism-token prism-atrule]":[#decorator]: "Dekorátor .[prism-token prism-comment]"
diff --git a/dependency-injection/cs/faq.texy b/dependency-injection/cs/faq.texy index e9842c0bba..a64b806cbe 100644 --- a/dependency-injection/cs/faq.texy +++ b/dependency-injection/cs/faq.texy @@ -88,7 +88,7 @@ Mějme na paměti [Pravidlo č. 1: nech si to předat |introduction#Pravidlo č. V této ukázce je `%myParameter%` zástupný symbol pro hodnotu parametru `myParameter`, který se předá do konstruktoru třídy `MyClass`: -```php +```neon # config.neon parameters: myParameter: Some value diff --git a/dependency-injection/cs/global-state.texy b/dependency-injection/cs/global-state.texy index d152d69973..2f7ddf5caf 100644 --- a/dependency-injection/cs/global-state.texy +++ b/dependency-injection/cs/global-state.texy @@ -93,7 +93,7 @@ Musíte podrobně procházet kód, abyste zjistili, že objekt `PaymentGateway` ```php $db = new DB('mysql:', 'user', 'password'); -$gateway = new PaymentGateway($db, ...); +$gateway = new PaymentGateway($db, /* ... */); ``` Podobný problém se objevuje i při použití globálního přístupu k databázovému spojení: diff --git a/dependency-injection/cs/services.texy b/dependency-injection/cs/services.texy index 8a86b230e9..6f4ec1bacb 100644 --- a/dependency-injection/cs/services.texy +++ b/dependency-injection/cs/services.texy @@ -433,8 +433,14 @@ services: application.application: create: MyApplication alteration: true - setup: - - '$onStartup[]' = [@resource, init] +``` + +Službu nemusíte identifikovat interním názvem, můžete na ni odkázat i jejím typem. Předchozí příklad tak lze zapsat i takto: + +```neon +services: + @Nette\Application\Application: + create: MyApplication ``` Při přepisování služby můžeme chtít odstranit původní argumenty, položky setup nebo tagy, k čemuž slouží `reset`: diff --git a/dependency-injection/de/configuration.texy b/dependency-injection/de/configuration.texy index 27e564e3b5..5eeed62bf7 100644 --- a/dependency-injection/de/configuration.texy +++ b/dependency-injection/de/configuration.texy @@ -8,7 +8,7 @@ Konfiguration des DI-Containers Konfigurationsdatei =================== -Der Nette DI Container lässt sich leicht über Konfigurationsdateien steuern. Diese werden normalerweise im [NEON-Format|neon:format] geschrieben. Zur Bearbeitung empfehlen wir [Editoren mit Unterstützung |best-practices:editors-and-tools#IDE-Editor] für dieses Format. +Der Nette DI Container lässt sich leicht über Konfigurationsdateien steuern. Diese werden normalerweise im [NEON-Format|neon:format] geschrieben. Zur Bearbeitung empfehlen wir [Editoren mit Unterstützung |tools:ide] für dieses Format."decorator .[prism-token prism-atrule]":[#Decorator]: "Decorator .[prism-token prism-comment]"
diff --git a/dependency-injection/el/configuration.texy b/dependency-injection/el/configuration.texy index 03e6ecb64d..a90b11cf1f 100644 --- a/dependency-injection/el/configuration.texy +++ b/dependency-injection/el/configuration.texy @@ -8,7 +8,7 @@ Αρχείο διαμόρφωσης ================== -Το Nette DI Container ελέγχεται εύκολα μέσω αρχείων διαμόρφωσης. Αυτά συνήθως γράφονται σε [μορφή NEON |neon:format]. Για την επεξεργασία, συνιστούμε [editors με υποστήριξη |best-practices:editors-and-tools#IDE editor] αυτής της μορφής. +Το Nette DI Container ελέγχεται εύκολα μέσω αρχείων διαμόρφωσης. Αυτά συνήθως γράφονται σε [μορφή NEON |neon:format]. Για την επεξεργασία, συνιστούμε [editors με υποστήριξη |tools:ide] αυτής της μορφής."decorator .[prism-token prism-atrule]":[#decorator]: "Decorator .[prism-token prism-comment]"
diff --git a/dependency-injection/en/configuration.texy b/dependency-injection/en/configuration.texy index 7f42e4bc97..ba876eb354 100644 --- a/dependency-injection/en/configuration.texy +++ b/dependency-injection/en/configuration.texy @@ -8,7 +8,7 @@ Overview of configuration options for the Nette DI container. Configuration File ================== -The Nette DI container is easily controlled using configuration files. These are usually written in the [NEON format|neon:format]. We recommend using [editors with support |best-practices:editors-and-tools#IDE Editor] for this format. +The Nette DI container is easily controlled using configuration files. These are usually written in the [NEON format|neon:format]. We recommend using [editors with support |tools:ide] for this format."decorator .[prism-token prism-atrule]":[#Decorator]: "Decorator .[prism-token prism-comment]"
diff --git a/dependency-injection/en/faq.texy b/dependency-injection/en/faq.texy index b7f65321ba..6f8b836dc9 100644 --- a/dependency-injection/en/faq.texy +++ b/dependency-injection/en/faq.texy @@ -88,7 +88,7 @@ Keep in mind [Rule #1: Let It Be Passed to You |introduction#Rule #1: Let It Be In this example, `%myParameter%` is a placeholder for the value of the `myParameter` parameter, which will be passed to the `MyClass` constructor: -```php +```neon # config.neon parameters: myParameter: Some value diff --git a/dependency-injection/en/global-state.texy b/dependency-injection/en/global-state.texy index f626392831..794f2814b3 100644 --- a/dependency-injection/en/global-state.texy +++ b/dependency-injection/en/global-state.texy @@ -93,7 +93,7 @@ You must meticulously trace the code to discover that the `PaymentGateway` objec ```php $db = new DB('mysql:', 'user', 'password'); -$gateway = new PaymentGateway($db, ...); +$gateway = new PaymentGateway($db, /* ... */); ``` A similar problem arises when using global access to a database connection: diff --git a/dependency-injection/en/services.texy b/dependency-injection/en/services.texy index d724a229cf..8da30d74ed 100644 --- a/dependency-injection/en/services.texy +++ b/dependency-injection/en/services.texy @@ -433,8 +433,14 @@ services: application.application: create: MyApplication alteration: true - setup: - - '$onStartup[]' = [@resource, init] +``` + +You don't have to identify a service by its internal name — you can refer to it by type instead. The previous example can also be written as: + +```neon +services: + @Nette\Application\Application: + create: MyApplication ``` When modifying a service, we might want to remove original arguments, setup items, or tags, using the `reset` key: diff --git a/dependency-injection/es/configuration.texy b/dependency-injection/es/configuration.texy index 8d3c0151ae..c4ab65c181 100644 --- a/dependency-injection/es/configuration.texy +++ b/dependency-injection/es/configuration.texy @@ -8,7 +8,7 @@ Resumen de las opciones de configuración para el contenedor Nette DI. Archivo de configuración ======================== -El contenedor Nette DI se controla fácilmente mediante archivos de configuración. Normalmente se escriben en [formato NEON |neon:format]. Para la edición, recomendamos [editores con soporte |best-practices:editors-and-tools#Editor IDE] para este formato. +El contenedor Nette DI se controla fácilmente mediante archivos de configuración. Normalmente se escriben en [formato NEON |neon:format]. Para la edición, recomendamos [editores con soporte |tools:ide] para este formato."decorator .[prism-token prism-atrule]":[#decorator]: "Decorador .[prism-token prism-comment]"
diff --git a/dependency-injection/fr/configuration.texy b/dependency-injection/fr/configuration.texy index a5167ac052..8b37bad212 100644 --- a/dependency-injection/fr/configuration.texy +++ b/dependency-injection/fr/configuration.texy @@ -8,7 +8,7 @@ Aperçu des options de configuration pour le conteneur Nette DI. Fichier de configuration ======================== -Le conteneur Nette DI est facilement contrôlé à l'aide de fichiers de configuration. Ceux-ci sont généralement écrits au [format NEON |neon:format]. Pour l'édition, nous recommandons des [éditeurs avec support |best-practices:editors-and-tools#Éditeur IDE] pour ce format. +Le conteneur Nette DI est facilement contrôlé à l'aide de fichiers de configuration. Ceux-ci sont généralement écrits au [format NEON |neon:format]. Pour l'édition, nous recommandons des [éditeurs avec support |tools:ide] pour ce format."decorator .[prism-token prism-atrule]":[#Decorator]: "Décorateur .[prism-token prism-comment]"
diff --git a/dependency-injection/hu/configuration.texy b/dependency-injection/hu/configuration.texy index a4f889fa85..86547cb880 100644 --- a/dependency-injection/hu/configuration.texy +++ b/dependency-injection/hu/configuration.texy @@ -8,7 +8,7 @@ A Nette DI konténer konfigurációs opcióinak áttekintése. Konfigurációs fájl ================== -A Nette DI konténer könnyen vezérelhető konfigurációs fájlok segítségével. Ezek általában [NEON formátumban|neon:format] íródnak. A szerkesztéshez [támogatással rendelkező szerkesztőket |best-practices:editors-and-tools#IDE szerkesztő] ajánlunk ehhez a formátumhoz. +A Nette DI konténer könnyen vezérelhető konfigurációs fájlok segítségével. Ezek általában [NEON formátumban|neon:format] íródnak. A szerkesztéshez [támogatással rendelkező szerkesztőket |tools:ide] ajánlunk ehhez a formátumhoz."decorator .[prism-token prism-atrule]":[#Decorator]: "Dekorátor .[prism-token prism-comment]"
diff --git a/dependency-injection/it/configuration.texy b/dependency-injection/it/configuration.texy index 7155ff7ebf..2b484c11b2 100644 --- a/dependency-injection/it/configuration.texy +++ b/dependency-injection/it/configuration.texy @@ -8,7 +8,7 @@ Panoramica delle opzioni di configurazione per il container Nette DI. File di configurazione ====================== -Il container Nette DI è facilmente controllabile tramite file di configurazione. Questi sono solitamente scritti nel [formato NEON|neon:format]. Per la modifica, consigliamo [editor con supporto |best-practices:editors-and-tools#Editor IDE] per questo formato. +Il container Nette DI è facilmente controllabile tramite file di configurazione. Questi sono solitamente scritti nel [formato NEON|neon:format]. Per la modifica, consigliamo [editor con supporto |tools:ide] per questo formato."decorator .[prism-token prism-atrule]":[#Decorator]: "Decorator .[prism-token prism-comment]"
diff --git a/dependency-injection/ja/configuration.texy b/dependency-injection/ja/configuration.texy index b9510329cf..f6c4f5403e 100644 --- a/dependency-injection/ja/configuration.texy +++ b/dependency-injection/ja/configuration.texy @@ -8,7 +8,7 @@ Nette DIコンテナの設定オプションの概要。 設定ファイル ====== -Nette DIコンテナは、設定ファイルを使用して簡単に制御できます。これらは通常、[NEON形式|neon:format]で記述されます。編集には、この形式を[サポートするエディタ |best-practices:editors-and-tools#IDEエディタ]をお勧めします。 +Nette DIコンテナは、設定ファイルを使用して簡単に制御できます。これらは通常、[NEON形式|neon:format]で記述されます。編集には、この形式を[サポートするエディタ |tools:ide]をお勧めします。"decorator .[prism-token prism-atrule]":[#decorator]: "デコレータ .[prism-token prism-comment]"
diff --git a/dependency-injection/pl/configuration.texy b/dependency-injection/pl/configuration.texy index d56a300da7..f487322922 100644 --- a/dependency-injection/pl/configuration.texy +++ b/dependency-injection/pl/configuration.texy @@ -8,7 +8,7 @@ Przegląd opcji konfiguracyjnych dla kontenera Nette DI. Plik konfiguracyjny =================== -Kontener Nette DI łatwo się kontroluje za pomocą plików konfiguracyjnych. Zazwyczaj są one zapisywane w [formacie NEON|neon:format]. Do edycji polecamy [edytory z obsługą |best-practices:editors-and-tools#Edytor IDE] tego formatu. +Kontener Nette DI łatwo się kontroluje za pomocą plików konfiguracyjnych. Zazwyczaj są one zapisywane w [formacie NEON|neon:format]. Do edycji polecamy [edytory z obsługą |tools:ide] tego formatu."decorator .[prism-token prism-atrule]":[#decorator]: "Dekorator .[prism-token prism-comment]"
diff --git a/dependency-injection/pt/configuration.texy b/dependency-injection/pt/configuration.texy index e22c943046..000779a38b 100644 --- a/dependency-injection/pt/configuration.texy +++ b/dependency-injection/pt/configuration.texy @@ -8,7 +8,7 @@ Visão geral das opções de configuração para o contêiner Nette DI. Arquivo de Configuração ======================= -O contêiner Nette DI é facilmente controlado por meio de arquivos de configuração. Eles geralmente são escritos no [formato NEON|neon:format]. Para edição, recomendamos [editores com suporte |best-practices:editors-and-tools#Editor IDE] para este formato. +O contêiner Nette DI é facilmente controlado por meio de arquivos de configuração. Eles geralmente são escritos no [formato NEON|neon:format]. Para edição, recomendamos [editores com suporte |tools:ide] para este formato."decorator .[prism-token prism-atrule]":[#decorator]: "Decorador .[prism-token prism-comment]"
diff --git a/dependency-injection/ro/configuration.texy b/dependency-injection/ro/configuration.texy index c78aeade7a..250662de49 100644 --- a/dependency-injection/ro/configuration.texy +++ b/dependency-injection/ro/configuration.texy @@ -8,7 +8,7 @@ Prezentare generală a opțiunilor de configurare pentru containerul Nette DI. Fișier de configurare ===================== -Containerul Nette DI este ușor de controlat folosind fișiere de configurare. Acestea sunt de obicei scrise în [formatul NEON |neon:format]. Pentru editare, recomandăm [editoare cu suport |best-practices:editors-and-tools#Editor IDE] pentru acest format. +Containerul Nette DI este ușor de controlat folosind fișiere de configurare. Acestea sunt de obicei scrise în [formatul NEON |neon:format]. Pentru editare, recomandăm [editoare cu suport |tools:ide] pentru acest format."decorator .[prism-token prism-atrule]":[#decorator]: "Decorator .[prism-token prism-comment]"
diff --git a/dependency-injection/ru/configuration.texy b/dependency-injection/ru/configuration.texy index bc18331de9..c7fad100ee 100644 --- a/dependency-injection/ru/configuration.texy +++ b/dependency-injection/ru/configuration.texy @@ -8,7 +8,7 @@ Файл конфигурации ================= -DI-контейнер Nette легко управляется с помощью файлов конфигурации. Они обычно записываются в [формате NEON |neon:format]. Для редактирования рекомендуем [редакторы с поддержкой |best-practices:editors-and-tools#IDE редактор] этого формата. +DI-контейнер Nette легко управляется с помощью файлов конфигурации. Они обычно записываются в [формате NEON |neon:format]. Для редактирования рекомендуем [редакторы с поддержкой |tools:ide] этого формата."decorator .[prism-token prism-atrule]":[#decorator]: "Декоратор .[prism-token prism-comment]"
diff --git a/dependency-injection/sl/configuration.texy b/dependency-injection/sl/configuration.texy index d4b773c1a5..f025ad8f51 100644 --- a/dependency-injection/sl/configuration.texy +++ b/dependency-injection/sl/configuration.texy @@ -8,7 +8,7 @@ Pregled konfiguracijskih možnosti za Nette DI vsebnik. Konfiguracijska datoteka ======================== -Nette DI vsebnik se enostavno upravlja s konfiguracijskimi datotekami. Te se običajno zapisujejo v [formatu NEON|neon:format]. Za urejanje priporočamo [urejevalnike s podporo |best-practices:editors-and-tools#IDE urejevalnik] za ta format. +Nette DI vsebnik se enostavno upravlja s konfiguracijskimi datotekami. Te se običajno zapisujejo v [formatu NEON|neon:format]. Za urejanje priporočamo [urejevalnike s podporo |tools:ide] za ta format."decorator .[prism-token prism-atrule]":[#decorator]: "Dekorator .[prism-token prism-comment]"
diff --git a/dependency-injection/tr/configuration.texy b/dependency-injection/tr/configuration.texy index 41b28cddb2..4745a572c6 100644 --- a/dependency-injection/tr/configuration.texy +++ b/dependency-injection/tr/configuration.texy @@ -8,7 +8,7 @@ Nette DI konteyneri için yapılandırma seçeneklerine genel bakış. Yapılandırma Dosyası ==================== -Nette DI konteyneri, yapılandırma dosyaları aracılığıyla kolayca kontrol edilir. Bunlar genellikle [NEON formatı |neon:format] kullanılarak yazılır. Düzenleme için bu formatı [destekleyen düzenleyiciler |best-practices:editors-and-tools#IDE Editörü] öneririz. +Nette DI konteyneri, yapılandırma dosyaları aracılığıyla kolayca kontrol edilir. Bunlar genellikle [NEON formatı |neon:format] kullanılarak yazılır. Düzenleme için bu formatı [destekleyen düzenleyiciler |tools:ide] öneririz."decorator .[prism-token prism-atrule]":[#Dekoratör Decorator]: "Dekoratör .[prism-token prism-comment]"
diff --git a/dependency-injection/uk/configuration.texy b/dependency-injection/uk/configuration.texy index be8b7c4869..fecac3fb0c 100644 --- a/dependency-injection/uk/configuration.texy +++ b/dependency-injection/uk/configuration.texy @@ -8,7 +8,7 @@ Конфігураційний файл ==================== -Nette DI-контейнер легко керується за допомогою конфігураційних файлів. Вони зазвичай записуються у [форматі NEON|neon:format]. Для редагування рекомендуємо [редактори з підтримкою |best-practices:editors-and-tools#IDE редактор] цього формату. +Nette DI-контейнер легко керується за допомогою конфігураційних файлів. Вони зазвичай записуються у [форматі NEON|neon:format]. Для редагування рекомендуємо [редактори з підтримкою |tools:ide] цього формату."decorator .[prism-token prism-atrule]":[#decorator]: "Декоратор .[prism-token prism-comment]"
diff --git a/forms/cs/in-presenter.texy b/forms/cs/in-presenter.texy index 2c827d31f7..d49fa4168e 100644 --- a/forms/cs/in-presenter.texy +++ b/forms/cs/in-presenter.texy @@ -359,7 +359,7 @@ Zmíněný CSRF útok spočívá v tom, že útočník naláká oběť na strán $form->allowCrossOrigin(); // POZOR! Vypne ochranu! ``` -Tato ochrana využívá SameSite cookie pojmenovanou `_nss`. Ochrana pomocí SameSite cookie nemusí být 100% spolehlivá, proto je vhodné zapnout ještě ochranu pomocí tokenu: +Tato ochrana se opírá o hlavičky `Sec-Fetch-*` (Fetch Metadata), které prohlížeč posílá automaticky. Starší prohlížeče, které je neposílají, nejsou pokryté, proto je vhodné zapnout ještě ochranu pomocí tokenu: ```php $form->addProtection(); diff --git a/forms/cs/standalone.texy b/forms/cs/standalone.texy index 1b980ceeda..51153c9d33 100644 --- a/forms/cs/standalone.texy +++ b/forms/cs/standalone.texy @@ -304,9 +304,9 @@ Zmíněný CSRF útok spočívá v tom, že útočník naláká oběť na strán $form->allowCrossOrigin(); // POZOR! Vypne ochranu! ``` -Tato ochrana využívá SameSite cookie pojmenovanou `_nss`. Vytvářejte proto objekt formuláře ještě před odesláním prvního výstupu, aby bylo možné cookie odeslat. +Tato ochrana se opírá o hlavičky `Sec-Fetch-*` (Fetch Metadata), které prohlížeč posílá automaticky s požadavkem. -Ochrana pomocí SameSite cookie nemusí být 100% spolehlivá, proto je vhodné zapnout ještě ochranu pomocí tokenu: +Starší prohlížeče, které tyto hlavičky neposílají, nejsou pokryté, proto je vhodné zapnout ještě ochranu pomocí tokenu: ```php $form->addProtection(); diff --git a/forms/en/in-presenter.texy b/forms/en/in-presenter.texy index b4cde43641..1dd522dc93 100644 --- a/forms/en/in-presenter.texy +++ b/forms/en/in-presenter.texy @@ -359,7 +359,7 @@ The mentioned CSRF attack involves an attacker luring a victim to a page that si $form->allowCrossOrigin(); // WARNING! Disables protection! ``` -This protection uses a SameSite cookie named `_nss`. SameSite cookie protection might not be 100% reliable, so it's advisable to also enable token protection: +This protection relies on the browser's `Sec-Fetch-*` headers (Fetch Metadata), which it sends automatically. Older browsers that don't send them are not covered, so it's advisable to also enable token protection: ```php $form->addProtection(); diff --git a/forms/en/standalone.texy b/forms/en/standalone.texy index 0260668a0e..8cb60618aa 100644 --- a/forms/en/standalone.texy +++ b/forms/en/standalone.texy @@ -304,9 +304,9 @@ The mentioned CSRF attack involves an attacker luring a victim to a page that si $form->allowCrossOrigin(); // WARNING! Disables protection! ``` -This protection uses a SameSite cookie named `_nss`. Therefore, create the form object before sending the first output so that the cookie can be sent. +This protection relies on the browser's `Sec-Fetch-*` headers (Fetch Metadata), which it sends automatically with the request. -SameSite cookie protection may not be 100% reliable, so it's advisable to also enable token protection: +Older browsers that don't send these headers are not covered, so it's advisable to also enable token protection: ```php $form->addProtection(); diff --git a/http/cs/@home.texy b/http/cs/@home.texy index 47814c400d..8442d4fdbb 100644 --- a/http/cs/@home.texy +++ b/http/cs/@home.texy @@ -2,7 +2,14 @@ Nette HTTP ********** .[perex] -Balíček `nette/http` zapouzdřuje [HTTP request|request] & [response], práci se [sessions] a [parsování a skládání URL |urls]. +Balíček `nette/http` je vaším pomocníkem pro veškerou komunikaci přes HTTP. Poskytuje srozumitelné objektové API nad příchozím požadavkem a odchozí odpovědí, usnadňuje práci se sessions i s URL adresami a navíc se postará o bezpečnost. Co v něm najdete: + +| [HTTP request |request] | příchozí požadavek a sanitizace vstupů +| [HTTP response |response] | odchozí odpověď, hlavičky a cookies +| [Sessions] | bezpečné uchování stavu mezi požadavky +| [Práce s URL |urls] | parsování a skládání URL adres +| [Ochrana proti SSRF |ssrf] | obrana proti útokům Server-Side Request Forgery +| [Konfigurace |configuration] | konfigurační volby balíčku Instalace diff --git a/http/cs/@left-menu.texy b/http/cs/@left-menu.texy index 8753fc54a5..59dfcdcb47 100644 --- a/http/cs/@left-menu.texy +++ b/http/cs/@left-menu.texy @@ -4,5 +4,6 @@ Nette HTTP - [HTTP request|request] - [HTTP response|response] - [Sessions] -- [URL utilities |urls] +- [Práce s URL |urls] +- [Ochrana proti SSRF |ssrf] - [Konfigurace |configuration] diff --git a/http/cs/configuration.texy b/http/cs/configuration.texy index 4d9bc137d9..cd73811488 100644 --- a/http/cs/configuration.texy +++ b/http/cs/configuration.texy @@ -108,6 +108,18 @@ http: ``` +Vynucení HTTPS .{data-version:3.3.4} +------------------------------------ + +Bezpodmínečně vynutí HTTPS schéma požadavku. Hodí se pro weby běžící výhradně na HTTPS za load balancerem nebo reverzní proxy, která terminuje TLS, ale neposílá hlavičku `X-Forwarded-Proto`, takže by standardní detekce HTTPS (ani s nastavenou [#HTTP proxy]) nezabrala. + +```neon +http: + # vynutí HTTPS schéma u všech požadavků + forceHttps: true # (bool) výchozí je false +``` + + Session ======= diff --git a/http/cs/request.texy b/http/cs/request.texy index 1827f489e0..f47f8e0a33 100644 --- a/http/cs/request.texy +++ b/http/cs/request.texy @@ -146,9 +146,41 @@ isSecured(): bool .[method] Je spojení šifrované (HTTPS)? Pro správnou funkčnost může být potřeba [nastavit proxy |configuration#HTTP proxy]. -isSameSite(): bool .[method] ----------------------------- -Přichází požadavek ze stejné (sub)domény a je iniciován kliknutím na odkaz? Nette k detekci používá cookie `_nss` (dříve `nette-samesite`). +isSameSite(): bool .[method deprecated] +--------------------------------------- +Přišel požadavek ze stejné stránky (same-site)? Od verze 3.4 ji nahrazuje schopnější metoda [isFrom() |#isFrom]. + + +isFrom(FetchSite|array $site, FetchDest|array|null $dest=null, ?bool $user=null): bool .[method]{data-version:3.4} +------------------------------------------------------------------------------------------------------------------ +Řekne vám, odkud požadavek přišel a jak ho prohlížeč vytvořil, na základě hlaviček `Sec-Fetch-*` (tzv. [Fetch Metadata |https://developer.mozilla.org/en-US/docs/Glossary/Fetch_metadata_request_header]), které prohlížeč nastavuje sám a stránka běžící v prohlížeči oběti je nedokáže zfalšovat ani odstranit. Nette ji interně používá k automatické ochraně formulářů a signálů před útoky [Cross-Site Request Forgery |nette:glossary#Cross-Site Request Forgery CSRF] (CSRF). Hodí se, když chcete ochránit vlastní citlivé akce, jako jsou API endpointy nebo destruktivní odkazy. + +Metoda vrátí `true` pouze tehdy, když požadavek splňuje **všechny** zadané podmínky. První parametr `$site` popisuje vztah mezi stránkou, která požadavek vyvolala, a vaším webem (hlavička `Sec-Fetch-Site`). Přijímá jednu hodnotu nebo pole těchto hodnot výčtu `FetchSite`: + +- `FetchSite::SameOrigin` – ze zcela stejného původu (schéma, host i port) +- `FetchSite::SameSite` – ze stejného webu, případně z jiné subdomény +- `FetchSite::CrossSite` – z cizího webu +- `FetchSite::None` – uživatel jej vyvolal přímo, např. zadáním URL nebo otevřením záložky + +```php +// přišel požadavek z našich vlastních stránek? +if (!$httpRequest->isFrom([FetchSite::SameOrigin, FetchSite::SameSite])) { + // akci zablokujeme +} +``` + +Volitelný parametr `$dest` (hlavička `Sec-Fetch-Dest`) udává, jaký druh zdroje prohlížeč načítá, např. `FetchDest::Document` pro navigaci na stránku nebo `FetchDest::Empty` pro požadavek z JavaScriptu. Volitelný parametr `$user` (hlavička `Sec-Fetch-User`) říká, zda navigaci vyvolala skutečná akce uživatele, jako kliknutí na odkaz či odeslání formuláře; hodnotou `true` ji vyžadujete. + +Kontrola, že je akce dostupná pouze z vlastních stránek a jen skutečnou akcí uživatele, pak vypadá takto: + +```php +if (!$httpRequest->isFrom(FetchSite::SameOrigin, FetchDest::Document, user: true)) { + $this->error(); +} +``` + +.[note] +Starší prohlížeče (Safari před 16.4) hlavičky `Sec-Fetch-*` neposílají. Pro ně Nette použije záložně cookie `SameSite=Strict`, která dokazuje pouze to, že požadavek není cross-site. Kontrolu, která navíc vyžaduje `$dest` nebo `$user`, tak nelze tímto způsobem ověřit a v těchto prohlížečích vrátí `false` – pokud je to příliš striktní, testujte jen `$site`. isAjax(): bool .[method] @@ -236,6 +268,7 @@ RequestFactory lze před zavoláním `fromGlobals()` konfigurovat: - metodou `$factory->setBinary()` vypnete automatické čištění vstupních parametrů od kontrolních znaků a neplatných UTF-8 sekvencí. - metodou `$factory->setProxy(...)` uvedete IP adresu [proxy serveru |configuration#HTTP proxy], což je nezbytné pro správnou detekci IP adresy uživatele. +- metodou `$factory->setForceHttps()` .{data-version:3.3.4} vynutíte HTTPS schéma požadavku bez ohledu na prostředí serveru. RequestFactory umožňuje definovat filtry, které automaticky transformují části URL požadavku. Tyto filtry odstraňují nežádoucí znaky z URL, které tam mohou být vloženy například nesprávnou implementací komentářových systémů na různých webech: diff --git a/http/cs/response.texy b/http/cs/response.texy index 4732bc7a76..3ad5c46192 100644 --- a/http/cs/response.texy +++ b/http/cs/response.texy @@ -115,15 +115,16 @@ $httpResponse->sendAsFile('faktura.pdf'); ``` -setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite='Lax') .[method] --------------------------------------------------------------------------------------------------------------------------------------------------------------------- +setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, SameSite|string $sameSite='Lax', bool $partitioned=false) .[method] +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Odešle cookie. Výchozí hodnoty parametrů: -| `$path` | `'/'` | cookie má dosah na všechny cesty v (sub)doméně *(konfigurovatelné)* -| `$domain` | `null` | což znamená s dosahem na aktuální (sub)doménu, ale nikoliv její subdomény *(konfigurovatelné)* -| `$secure` | `true` | pokud web běží na HTTPS, jinak `false` *(konfigurovatelné)* -| `$httpOnly` | `true` | cookie je pro JavaScript nepřístupná -| `$sameSite` | `'Lax'` | cookie nemusí být odeslána při [přístupu z jiné domény |nette:glossary#SameSite cookie] +| `$path` | `'/'` | cookie má dosah na všechny cesty v (sub)doméně *(konfigurovatelné)* +| `$domain` | `null` | což znamená s dosahem na aktuální (sub)doménu, ale nikoliv její subdomény *(konfigurovatelné)* +| `$secure` | `true` | pokud web běží na HTTPS, jinak `false` *(konfigurovatelné)* +| `$httpOnly` | `true` | cookie je pro JavaScript nepřístupná +| `$sameSite` | `'Lax'` | cookie nemusí být odeslána při [přístupu z jiné domény |nette:glossary#SameSite cookie] +| `$partitioned` | `false` | zda je cookie partitioned, viz níže *(od verze 3.4)* Výchozí hodnoty parametrů `$path`, `$domain` a `$secure` můžete změnit v [konfiguraci |configuration#HTTP cookie]. @@ -135,7 +136,14 @@ $httpResponse->setCookie('lang', 'cs', '100 days'); Parametr `$domain` určuje, které domény mohou cookie přijímat. Není-li uveden, cookie přijímá stejná (sub)doména, jako ji nastavila, ale nikoliv její subdomény. Pokud je `$domain` zadaný, jsou zahrnuty i subdomény. Proto je uvedení `$domain` méně omezující než vynechání. Například při `$domain = 'nette.org'` jsou cookies dostupné i na všech subdoménách jako `doc.nette.org`. -Pro hodnotu `$sameSite` můžete použít konstanty `Response::SameSiteLax`, `SameSiteStrict` a `SameSiteNone`. +Hodnotu `$sameSite` můžete předat jako enum `Nette\Http\SameSite` – `SameSite::Lax`, `SameSite::Strict` nebo `SameSite::None` (fungují i řetězce `'Lax'`, `'Strict'`, `'None'`). Pokud ji nastavíte na `SameSite::None`, automaticky se zapne atribut `$secure`, protože prohlížeče cookie se `SameSite=None` bez něj odmítají. + +.{data-version:3.4} +Partitioned cookies (CHIPS) dávají cookie samostatné úložiště pro každý web nejvyšší úrovně. Když tedy služba třetí strany (například vložený widget) nastaví partitioned cookie, prohlížeč pro každý web, na kterém se widget objeví, uchovává oddělenou kopii a tyto kopie nelze vzájemně propojit pro sledování napříč weby. Zapnete je nastavením `$partitioned` na `true`; to zároveň vyžaduje atribut `$secure`, takže se zapne automaticky. + +```php +$httpResponse->setCookie('theme', 'dark', '1 year', sameSite: SameSite::None, partitioned: true); +``` deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method] diff --git a/http/cs/sessions.texy b/http/cs/sessions.texy index de10e7715f..c004573654 100644 --- a/http/cs/sessions.texy +++ b/http/cs/sessions.texy @@ -183,8 +183,8 @@ setExpiration(?string $time): static .[method] Nastaví dobu neaktivity po které session vyexpiruje. -setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method] ---------------------------------------------------------------------------------------------------------------------- +setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, SameSite|string|null $samesite=null): static .[method] +---------------------------------------------------------------------------------------------------------------------------------- Nastavení parametrů pro cookie. Výchozí hodnoty parametrů můžete změnit v [konfiguraci |configuration#Session cookie]. diff --git a/http/cs/ssrf.texy b/http/cs/ssrf.texy new file mode 100644 index 0000000000..375a90f662 --- /dev/null +++ b/http/cs/ssrf.texy @@ -0,0 +1,183 @@ +Ochrana proti SSRF +****************** + +.[perex] +Když vaše aplikace stahuje URL zadanou uživatelem, může toho útočník zneužít a dostat se do vaší interní sítě. Třídy [#UrlValidator] a [#IPAddress] vám pomohou bránit se těmto útokům Server-Side Request Forgery (SSRF). + +→ [Instalace a požadavky |@home#Instalace] + + +Co je SSRF? +=========== + +Představte si funkci, kde uživatel zadá URL a váš server ji stáhne – avatar ze vzdálené adresy, cíl webhooku, náhled odkazu. Vypadá to neškodně, ale na adresu se připojuje server, ne prohlížeč uživatele. A server vidí místa, kam útočník nedosáhne: loopback rozhraní, privátní síť, cloudové služby. + +Útočník proto pošle URL, která místo na veřejný internet míří dovnitř. Typickými cíli jsou: + +- cloudová metadata na `http://169.254.169.254/`, která mohou prozradit přístupové klíče +- interní administrace a routery jako `http://192.168.1.1/` +- služby bez autentizace, například Redis na `http://localhost:6379/` + +Tato třída zranitelností je tak rozšířená, že patří mezi [OWASP Top 10 |https://owasp.org/Top10/]. Obranou je ověřit URL **dříve**, než ji stáhnete, a odmítnout vše, co se přeloží na neveřejnou adresu. + + +UrlValidator +============ + +[api:Nette\Http\UrlValidator] ověřuje URL proti konfigurovatelné politice: schéma, port, host, userinfo a IP adresy, na které se host přeloží. Základní použití je jediné volání: + +```php +use Nette\Http\UrlValidator; + +if (!(new UrlValidator)->allows($userUrl)) { + return; // nebezpečná URL, nestahujte ji +} +``` + +Výchozí politika je záměrně přísná – akceptuje pouze `https` na portu 443 mířící na veřejnou IP adresu. Vše ostatní (loopback, privátní rozsahy, link-local včetně cloudových metadat, rezervované rozsahy) je odmítnuto a multicast je odmítnut bezpodmínečně. To je správný výchozí bod pro stahování libovolných URL zadaných uživatelem. + + +Konfigurace politiky +-------------------- + +Politiku tvarujete přes konstruktor. Například chcete-li povolit prosté `http` na libovolném portu a dosáhnout na privátní adresy (užitečné uvnitř důvěryhodné sítě): + +```php +$validator = new UrlValidator( + schemes: ['http', 'https'], + ports: null, // libovolný port + allowPrivateIps: true, +); +``` + +Častým vzorem je omezit stahování na pevnou sadu partnerských domén pomocí allowlistu hostů. Prefix `*.` odpovídá libovolné hloubce subdomény, ale ne samotné doméně – pokud ji potřebujete, uveďte oba tvary: + +```php +$validator = new UrlValidator( + hostAllowlist: ['example.com', '*.example.com'], +); +``` + +Kompletní sada možností konstruktoru: + +| Parametr | Výchozí | Význam +|--------------------- +| `schemes` | `['https']` | povolená schémata; `[]` odmítne vše +| `ports` | `[443]` | povolené porty, `null` = libovolný; implicitní port ze schématu je respektován +| `allowPrivateIps` | `false` | povolit privátní rozsahy (10/8, 172.16/12, 192.168/16, fc00::/7) +| `allowLoopback` | `false` | povolit loopback (127.0.0.0/8, ::1) +| `allowLinkLocal` | `false` | povolit link-local vč. cloudových metadat 169.254.169.254 +| `allowReserved` | `false` | povolit rozsahy rezervované IANA +| `allowUserinfo` | `false` | povolit `user:pass@` v URL +| `hostAllowlist` | `null` | pokud je nastaven, host musí odpovídat jednomu vzoru; `[]` odmítne vše +| `hostBlocklist` | `null` | pokud je nastaven, host nesmí odpovídat žádnému vzoru + + +Metody validace +-------------- + +Validátor nabízí tři metody. `allows()` provede plnou kontrolu včetně překladu DNS – host se přeloží a **každá** A/AAAA adresa musí projít IP politikou: + +```php +(new UrlValidator)->allows($url); // bool +``` + +`allowsWithoutDns()` přeskakuje překlad DNS a kontroly IP rozsahů. Použijte ji jako rychlý předfiltr, nebo když je validace DNS delegována na stahovací vrstvu: + +```php +(new UrlValidator)->allowsWithoutDns($url); // bool +``` + +Obě metody přijímají řetězec, objekt [UrlImmutable |urls#UrlImmutable] nebo `null` (které vždy selže). + + +Obrana proti DNS rebindingu +-------------------------- + +Mezi validací a stažením je záludný souboj: útočník může při ověřování hostu vrátit bezpečnou IP a poté pro samotné stažení přepnout DNS na interní IP. K uzavření této díry vrací `getResolvedIPs()` ověřené IP adresy a vy na ně připnete spojení, aby stahování nešlo přesměrovat jinam: + +```php +$ips = (new UrlValidator)->getResolvedIPs($url); +if (!$ips) { + return; // nebezpečná URL +} + +$ch = curl_init($url); +$host = parse_url($url, PHP_URL_HOST); +curl_setopt($ch, CURLOPT_RESOLVE, ["$host:443:" . implode(',', $ips)]); +// ... proveďte požadavek +``` + +Metoda vrací pole IP řetězců (nejprve A záznamy, poté AAAA), které prošly celou politikou, nebo prázdné pole při jakémkoli selhání. Pro IP literál v URL ověří adresu přímo a žádný překlad DNS neprovádí. + + +IPAddress +========= + +[api:Nette\Http\IPAddress] je neměnný hodnotový objekt pro práci s IPv4 a IPv6 adresami. `UrlValidator` jej využívá interně, ale hodí se i samostatně, kdykoli adresy klasifikujete. Konstruktor vyhodí `Nette\InvalidArgumentException` u neplatné adresy: + +```php +use Nette\Http\IPAddress; + +$ip = new IPAddress('169.254.169.254'); +echo $ip; // '169.254.169.254' +``` + +Když nechcete výjimku, použijte tovární metodu `tryFrom()` nebo kontrolu `isValid()`: + +```php +$ip = IPAddress::tryFrom($input); // ?IPAddress +IPAddress::isValid($input); // bool +``` + + +Klasifikace adres +---------------- + +Predikáty říkají, do jaké třídy adresa patří. Klíčový je `isPublic()` – pravdivý jen pro veřejně směrovatelné adresy, což je přesně to, co obrana proti SSRF potřebuje: + +```php +$ip = new IPAddress('169.254.169.254'); +$ip->isPublic(); // false +$ip->isLinkLocal(); // true (rozsah cloudových metadat) +``` + +Kompletní sada predikátů: + +| Metoda | Testuje +|-------------------- +| `isPublic()` | veřejně směrovatelná (žádná z níže uvedených) +| `isPrivate()` | privátní rozsahy RFC 1918 / 4193 +| `isLoopback()` | 127.0.0.0/8, ::1 +| `isLinkLocal()` | 169.254.0.0/16 (vč. cloudových metadat), fe80::/10 +| `isMulticast()` | 224.0.0.0/4, ff00::/8 +| `isReserved()` | rezervováno IANA (dokumentace, CGNAT, budoucí použití, …) + + +Příslušnost k rozsahu +-------------------- + +`isInRange()` testuje, zda adresa spadá do CIDR bloku. Můžete předat síť s prefixem, nebo holou adresu pro přesnou shodu (implicitní /32 pro IPv4, /128 pro IPv6): + +```php +$ip = new IPAddress('192.168.1.50'); +$ip->isInRange('192.168.0.0/16'); // true +$ip->isInRange('10.0.0.1'); // false (přesná shoda) +``` + +Chybný vstup nebo jiná rodina IP vrací `false`. + + +IPv4-mapped IPv6 +---------------- + +Adresy zapsané jako IPv4-mapped IPv6 (například `::ffff:127.0.0.1`) jsou klasickým způsobem, jak proklouznout naivními filtry. `IPAddress` je normalizuje, takže predikáty rozsahů prohlédnou přestrojení: + +```php +$ip = new IPAddress('::ffff:127.0.0.1'); +$ip->isLoopback(); // true +$ip->isIPv4Mapped(); // true +$ip->toIPv4(); // IPAddress('127.0.0.1') +``` + +Metody `isIPv4()` a `isIPv6()` hlásí textový tvar: mapovaná adresa je IPv6, nikoli IPv4. diff --git a/http/en/@home.texy b/http/en/@home.texy index 9ae502ba2d..474b16672b 100644 --- a/http/en/@home.texy +++ b/http/en/@home.texy @@ -2,7 +2,14 @@ Nette HTTP ********** .[perex] -The `nette/http` package encapsulates [HTTP request|request] & [response], working with [sessions] and [URL parsing and building |urls]. +The `nette/http` package is your companion for all HTTP communication. It provides a clear object-oriented API over the incoming request and outgoing response, simplifies working with sessions and URL addresses, and takes care of security on top of that. Here's what you'll find: + +| [HTTP Request |request] | incoming request and input sanitization +| [HTTP Response |response] | outgoing response, headers and cookies +| [Sessions] | secure state persistence between requests +| [URL Utility |urls] | parsing and building URL addresses +| [SSRF Protection |ssrf] | defense against Server-Side Request Forgery attacks +| [Configuration] | configuration options of the package Installation diff --git a/http/en/@left-menu.texy b/http/en/@left-menu.texy index 652706062b..35f6b7d090 100644 --- a/http/en/@left-menu.texy +++ b/http/en/@left-menu.texy @@ -5,4 +5,5 @@ Nette HTTP - [HTTP Response |response] - [Sessions] - [URL Utility |urls] +- [SSRF Protection |ssrf] - [Configuration] diff --git a/http/en/configuration.texy b/http/en/configuration.texy index 64ce01eb2d..1537a3a2c4 100644 --- a/http/en/configuration.texy +++ b/http/en/configuration.texy @@ -108,6 +108,18 @@ http: ``` +Force HTTPS .{data-version:3.3.4} +--------------------------------- + +Unconditionally forces the request scheme to HTTPS. This is useful for HTTPS-only sites running behind a load balancer or reverse proxy that terminates TLS but does not pass the `X-Forwarded-Proto` header, so the standard HTTPS detection (even with [#HTTP Proxy] configured) would not catch it. + +```neon +http: + # force HTTPS scheme for all requests + forceHttps: true # (bool) defaults to false +``` + + Session ======= diff --git a/http/en/request.texy b/http/en/request.texy index 47e0232f54..d4651cf284 100644 --- a/http/en/request.texy +++ b/http/en/request.texy @@ -146,9 +146,41 @@ isSecured(): bool .[method] Is the connection encrypted (HTTPS)? Proper functionality might require [setting up a proxy |configuration#HTTP Proxy]. -isSameSite(): bool .[method] ----------------------------- -Is the request coming from the same (sub)domain and initiated by clicking a link? Nette uses the `_nss` cookie (formerly `nette-samesite`) for detection. +isSameSite(): bool .[method deprecated] +--------------------------------------- +Did the request come from the same site? Since version 3.4 it is replaced by the more capable [isFrom() |#isFrom]. + + +isFrom(FetchSite|array $site, FetchDest|array|null $dest=null, ?bool $user=null): bool .[method]{data-version:3.4} +------------------------------------------------------------------------------------------------------------------ +Tells you where the request came from and how the browser made it, based on the `Sec-Fetch-*` headers (so-called [Fetch Metadata |https://developer.mozilla.org/en-US/docs/Glossary/Fetch_metadata_request_header]) that the browser sets itself and a page running in the victim's browser can neither forge nor remove. Nette uses it internally to automatically protect forms and signals against [Cross-Site Request Forgery |nette:glossary#Cross-Site Request Forgery CSRF] (CSRF). It is useful when you want to guard your own sensitive actions, such as API endpoints or destructive links. + +The method returns `true` only when the request matches **all** the conditions you provide. The first parameter `$site` describes the relationship between the page that initiated the request and your site (the `Sec-Fetch-Site` header). It accepts a single value or a list of these `FetchSite` cases: + +- `FetchSite::SameOrigin` – from the exact same origin (scheme, host, and port) +- `FetchSite::SameSite` – from the same site, possibly a different subdomain +- `FetchSite::CrossSite` – from a foreign site +- `FetchSite::None` – the user initiated it directly, e.g. by typing the URL or opening a bookmark + +```php +// did the request originate from our own pages? +if (!$httpRequest->isFrom([FetchSite::SameOrigin, FetchSite::SameSite])) { + // block the action +} +``` + +The optional `$dest` parameter (the `Sec-Fetch-Dest` header) says what kind of resource the browser is fetching, e.g. `FetchDest::Document` for a top-level navigation or `FetchDest::Empty` for a request made from JavaScript. The optional `$user` parameter (the `Sec-Fetch-User` header) indicates whether the navigation was triggered by a genuine user action such as clicking a link or submitting a form; pass `true` to require it. + +A check that an action is reachable only from your own pages and only through a real user action then looks like this: + +```php +if (!$httpRequest->isFrom(FetchSite::SameOrigin, FetchDest::Document, user: true)) { + $this->error(); +} +``` + +.[note] +Older browsers (Safari before 16.4) do not send the `Sec-Fetch-*` headers. For them Nette falls back to a `SameSite=Strict` cookie that only proves the request is not cross-site. A check that additionally requires `$dest` or `$user` cannot be verified this way and returns `false` in those browsers – if that is too strict, test only `$site`. isAjax(): bool .[method] @@ -236,6 +268,7 @@ RequestFactory can be configured before calling `fromGlobals()`: - using the `$factory->setBinary()` method disables automatic cleansing of input parameters from control characters and invalid UTF-8 sequences. - using the `$factory->setProxy(...)` method specifies the IP address of the [proxy server |configuration#HTTP Proxy], which is necessary for correct detection of the user's IP address. +- using the `$factory->setForceHttps()` .{data-version:3.3.4} method forces the request scheme to HTTPS regardless of the server environment. RequestFactory allows defining filters that automatically transform parts of the URL request. These filters remove unwanted characters from URLs that might have been inserted, for example, by incorrect implementations of comment systems on various websites: diff --git a/http/en/response.texy b/http/en/response.texy index 7dbd4dca00..eeffb70588 100644 --- a/http/en/response.texy +++ b/http/en/response.texy @@ -115,15 +115,16 @@ $httpResponse->sendAsFile('invoice.pdf'); ``` -setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite='Lax') .[method] --------------------------------------------------------------------------------------------------------------------------------------------------------------------- +setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, SameSite|string $sameSite='Lax', bool $partitioned=false) .[method] +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sends a cookie. Default parameter values: -| `$path` | `'/'` | cookie is available for all paths within the (sub)domain *(configurable)* -| `$domain` | `null` | meaning available for the current (sub)domain, but not its subdomains *(configurable)* -| `$secure` | `true` | if the site is running on HTTPS, otherwise `false` *(configurable)* -| `$httpOnly` | `true` | cookie is inaccessible to JavaScript -| `$sameSite` | `'Lax'` | cookie might not be sent during [cross-origin access |nette:glossary#SameSite cookie] +| `$path` | `'/'` | cookie is available for all paths within the (sub)domain *(configurable)* +| `$domain` | `null` | meaning available for the current (sub)domain, but not its subdomains *(configurable)* +| `$secure` | `true` | if the site is running on HTTPS, otherwise `false` *(configurable)* +| `$httpOnly` | `true` | cookie is inaccessible to JavaScript +| `$sameSite` | `'Lax'` | cookie might not be sent during [cross-origin access |nette:glossary#SameSite cookie] +| `$partitioned` | `false` | whether the cookie is partitioned, see below *(since v3.4)* You can change the default values of the `$path`, `$domain`, and `$secure` parameters in the [configuration |configuration#HTTP Cookie]. @@ -135,7 +136,14 @@ $httpResponse->setCookie('lang', 'en', '100 days'); The `$domain` parameter determines which domains can accept the cookie. If not specified, the cookie is accepted by the same (sub)domain that set it, but not its subdomains. If `$domain` is specified, subdomains are also included. Therefore, specifying `$domain` is less restrictive than omitting it. For example, with `$domain = 'nette.org'`, cookies are also available on all subdomains like `doc.nette.org`. -You can use the constants `Response::SameSiteLax`, `Response::SameSiteStrict`, and `Response::SameSiteNone` for the `$sameSite` value. +You can pass the `$sameSite` value as a `Nette\Http\SameSite` enum – `SameSite::Lax`, `SameSite::Strict`, or `SameSite::None` (the string values `'Lax'`, `'Strict'`, `'None'` work too). If you set it to `SameSite::None`, the `$secure` attribute is enabled automatically, because browsers reject a `SameSite=None` cookie that is not secure. + +.{data-version:3.4} +Partitioned cookies (CHIPS) give a cookie its own separate storage for each top-level site. So when a third-party service (such as an embedded widget) sets a partitioned cookie, the browser keeps a distinct copy for every site the widget appears on, and these copies cannot be linked together for cross-site tracking. Turn it on by setting `$partitioned` to `true`; this also requires the `$secure` attribute, so it is enabled automatically. + +```php +$httpResponse->setCookie('theme', 'dark', '1 year', sameSite: SameSite::None, partitioned: true); +``` deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method] diff --git a/http/en/sessions.texy b/http/en/sessions.texy index f0831458d5..41d00779ae 100644 --- a/http/en/sessions.texy +++ b/http/en/sessions.texy @@ -183,8 +183,8 @@ setExpiration(?string $time): static .[method] Sets the inactivity time after which the session expires. -setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method] ---------------------------------------------------------------------------------------------------------------------- +setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, SameSite|string|null $samesite=null): static .[method] +---------------------------------------------------------------------------------------------------------------------------------- Sets parameters for cookies. You can change the default parameter values in the [configuration |configuration#Session Cookie]. diff --git a/http/en/ssrf.texy b/http/en/ssrf.texy new file mode 100644 index 0000000000..72d7251719 --- /dev/null +++ b/http/en/ssrf.texy @@ -0,0 +1,183 @@ +SSRF Protection +*************** + +.[perex] +When your application downloads a URL supplied by a user, an attacker can abuse it to reach your internal network. The [#UrlValidator] and [#IPAddress] classes help you guard against these Server-Side Request Forgery (SSRF) attacks. + +→ [Installation and requirements |@home#Installation] + + +What is SSRF? +============= + +Imagine a feature where the user enters a URL and your server downloads it – an avatar from a remote address, a webhook target, a link preview. It looks harmless, but the server reaches the address, not the user's browser. And the server can see places the attacker can't: the loopback interface, the private network, cloud services. + +An attacker therefore submits a URL that points inward instead of to the public internet. Typical targets are: + +- cloud metadata at `http://169.254.169.254/`, which can leak access keys +- internal admin panels and routers like `http://192.168.1.1/` +- services with no authentication, such as Redis on `http://localhost:6379/` + +This class of vulnerability is so common it ranks among the [OWASP Top 10 |https://owasp.org/Top10/]. The defense is to validate the URL **before** you fetch it and to refuse anything that resolves to a non-public address. + + +UrlValidator +============ + +[api:Nette\Http\UrlValidator] checks a URL against a configurable policy: the scheme, port, host, userinfo, and the IP addresses the host resolves to. The basic usage is a single call: + +```php +use Nette\Http\UrlValidator; + +if (!(new UrlValidator)->allows($userUrl)) { + return; // unsafe URL, do not fetch it +} +``` + +The default policy is deliberately strict – it only accepts `https` on port 443 pointing to a public IP address. Everything else (loopback, private ranges, link-local including cloud metadata, reserved ranges) is rejected, and multicast is rejected unconditionally. This is the right starting point for fetching arbitrary user-supplied URLs. + + +Configuring the Policy +---------------------- + +You shape the policy through the constructor. For example, to allow plain `http` on any port and reach private addresses (useful inside a trusted network): + +```php +$validator = new UrlValidator( + schemes: ['http', 'https'], + ports: null, // any port + allowPrivateIps: true, +); +``` + +A common pattern is to restrict fetching to a fixed set of partner domains using a host allowlist. The `*.` prefix matches any subdomain depth but not the apex – list both forms if you need it: + +```php +$validator = new UrlValidator( + hostAllowlist: ['example.com', '*.example.com'], +); +``` + +The full set of constructor options: + +| Parameter | Default | Meaning +|--------------------- +| `schemes` | `['https']` | allowed schemes; `[]` rejects everything +| `ports` | `[443]` | allowed ports, `null` = any; the implicit port from the scheme is honored +| `allowPrivateIps` | `false` | allow private ranges (10/8, 172.16/12, 192.168/16, fc00::/7) +| `allowLoopback` | `false` | allow loopback (127.0.0.0/8, ::1) +| `allowLinkLocal` | `false` | allow link-local incl. cloud metadata 169.254.169.254 +| `allowReserved` | `false` | allow IANA-reserved ranges +| `allowUserinfo` | `false` | allow `user:pass@` in the URL +| `hostAllowlist` | `null` | if set, host must match one pattern; `[]` rejects all +| `hostBlocklist` | `null` | if set, host must not match any pattern + + +Validation Methods +------------------ + +The validator offers three methods. `allows()` runs the full check including DNS resolution – the host is resolved and **every** A/AAAA address must pass the IP policy: + +```php +(new UrlValidator)->allows($url); // bool +``` + +`allowsWithoutDns()` skips DNS resolution and the IP-range checks. Use it as a fast pre-filter, or when DNS validation is delegated to the fetch layer: + +```php +(new UrlValidator)->allowsWithoutDns($url); // bool +``` + +Both methods accept a string, a [UrlImmutable |urls#UrlImmutable] object, or `null` (which always fails). + + +Defeating DNS Rebinding +----------------------- + +There is a subtle race between validation and fetching: an attacker can return a safe IP when you validate the host, then switch DNS to an internal IP for the actual download. To close this hole, `getResolvedIPs()` returns the validated IP addresses, and you pin the connection to them so the fetch can't be redirected elsewhere: + +```php +$ips = (new UrlValidator)->getResolvedIPs($url); +if (!$ips) { + return; // unsafe URL +} + +$ch = curl_init($url); +$host = parse_url($url, PHP_URL_HOST); +curl_setopt($ch, CURLOPT_RESOLVE, ["$host:443:" . implode(',', $ips)]); +// ... execute the request +``` + +The method returns an array of IP strings (A records first, then AAAA) that passed the full policy, or an empty array on any failure. For an IP literal in the URL it validates the address directly and performs no DNS lookup. + + +IPAddress +========= + +[api:Nette\Http\IPAddress] is an immutable value object for working with IPv4 and IPv6 addresses. `UrlValidator` uses it internally, but it's handy on its own whenever you classify addresses. The constructor throws `Nette\InvalidArgumentException` for an invalid address: + +```php +use Nette\Http\IPAddress; + +$ip = new IPAddress('169.254.169.254'); +echo $ip; // '169.254.169.254' +``` + +When you don't want an exception, use the `tryFrom()` factory or the `isValid()` checker: + +```php +$ip = IPAddress::tryFrom($input); // ?IPAddress +IPAddress::isValid($input); // bool +``` + + +Address Classification +---------------------- + +The predicates tell you which class an address belongs to. The key one is `isPublic()` – true only for publicly routable addresses, which is exactly what an SSRF guard wants: + +```php +$ip = new IPAddress('169.254.169.254'); +$ip->isPublic(); // false +$ip->isLinkLocal(); // true (cloud metadata range) +``` + +The full set of predicates: + +| Method | Tests for +|-------------------- +| `isPublic()` | publicly routable (none of the below) +| `isPrivate()` | RFC 1918 / 4193 private ranges +| `isLoopback()` | 127.0.0.0/8, ::1 +| `isLinkLocal()` | 169.254.0.0/16 (incl. cloud metadata), fe80::/10 +| `isMulticast()` | 224.0.0.0/4, ff00::/8 +| `isReserved()` | IANA-reserved (documentation, CGNAT, future-use, …) + + +Range Membership +---------------- + +`isInRange()` tests whether the address falls within a CIDR block. You can pass a network with a prefix, or a bare address for an exact match (implicit /32 for IPv4, /128 for IPv6): + +```php +$ip = new IPAddress('192.168.1.50'); +$ip->isInRange('192.168.0.0/16'); // true +$ip->isInRange('10.0.0.1'); // false (exact match) +``` + +Malformed input or a different IP family returns `false`. + + +IPv4-mapped IPv6 +---------------- + +Addresses written as IPv4-mapped IPv6 (such as `::ffff:127.0.0.1`) are a classic way to slip past naive filters. `IPAddress` normalizes them, so the range predicates see through the disguise: + +```php +$ip = new IPAddress('::ffff:127.0.0.1'); +$ip->isLoopback(); // true +$ip->isIPv4Mapped(); // true +$ip->toIPv4(); // IPAddress('127.0.0.1') +``` + +The `isIPv4()` and `isIPv6()` methods report the textual form: a mapped address is IPv6, not IPv4. diff --git a/http/en/urls.texy b/http/en/urls.texy index 608fa0064f..6c80b9485a 100644 --- a/http/en/urls.texy +++ b/http/en/urls.texy @@ -151,7 +151,7 @@ $newUrl = $url ->withPassword('') ->withPath('/en/'); -echo $newUrl; // 'http://john:xyz%2A12@nette.org:8080/en/?name=param#footer' +echo $newUrl; // 'http://nette.org:8080/en/?name=param#footer' ``` The `UrlImmutable` class implements the `JsonSerializable` interface and has a `__toString()` method, so the object can be printed or used in data passed to `json_encode()`. diff --git a/latte/bg/custom-tags.texy b/latte/bg/custom-tags.texy index 46bbf08171..23b5b4e001 100644 --- a/latte/bg/custom-tags.texy +++ b/latte/bg/custom-tags.texy @@ -923,7 +923,7 @@ class MyLatteExtension extends Extension Генериран HTML: -```html +```latte Изтриване ``` diff --git a/latte/bg/safety-first.texy b/latte/bg/safety-first.texy index 56a3b9fd91..6c7b2d9e1f 100644 --- a/latte/bg/safety-first.texy +++ b/latte/bg/safety-first.texy @@ -33,7 +33,7 @@ echo 'Резултати от търсенето за ' . $search . 'alert("Hacked!")`. Тъй като изходът не е обработен по никакъв начин, той става част от показаната страница: -```html +```latte
Резултати от търсенето за
``` @@ -59,7 +59,7 @@ echo ''; На нападателя е достатъчно като описание да вмъкне умело съставен низ `" onload="alert('Hacked!')` и ако изписването не е обработено, резултатният код ще изглежда така: -```html +```latte
``` @@ -91,7 +91,7 @@ echo '
'; Какво точно се разбира под думата контекст? Това е място в документа със собствени правила за обработка на извежданите данни. Зависи от типа на документа (HTML, XML, CSS, JavaScript, plain text, ...) и може да се различава в конкретните му части. Например в HTML документ има цяла редица такива места (контексти), където важат много различни правила. Може би ще се изненадате колко са. Ето първите четири: -```html +```latte
#текст
@@ -108,7 +108,7 @@ echo '
'; Контекстите също могат да се наслояват, което се случва, когато вмъкнем JavaScript или CSS в HTML. Това може да се направи по два различни начина, с елемент и с атрибут: -```html +```latte
@@ -132,7 +132,7 @@ echo '
'; Ако го извеждате в HTML текст, точно в този случай не е необходимо да правите никакви замени, защото низът не съдържа нито един знак със специално значение. Друга ситуация възниква, ако го изведете вътре в HTML атрибут, ограден с единични кавички. В такъв случай е необходимо да екранирате кавичките в HTML ентичности: -```html +```latte ``` @@ -152,13 +152,13 @@ alert('Rock\'n\'Roll'); Ако този код вмъкнем в HTML документ с помощта на ` alert('Rock\'n\'Roll'); ``` Ако обаче искахме да го вмъкнем в HTML атрибут, трябва още да екранираме кавичките в HTML ентичности: -```html +```latte ``` @@ -170,7 +170,7 @@ https://example.org/?a=Jazz&b=Rock%27n%27Roll И когато този низ изведем в атрибут, ще приложим още екраниране според този контекст и ще заменим `&` с `&`: -```html +```latte ``` @@ -314,7 +314,7 @@ Latte вижда шаблона по същия начин като вас. Ра Нападателят като описание на изображението вмъква умело съставен низ `foo onload=alert('Hacked!')`. Вече знаем, че Twig не може да разпознае дали променливата се извежда в потока на HTML текста, вътре в атрибут, HTML коментар и т.н., накратко не разграничава контексти. И само механично преобразува знаците `< > & ' "` в HTML ентичности. Така резултатният код ще изглежда така: -```html +```latte
``` @@ -330,7 +330,7 @@ Latte вижда шаблона по същия начин като вас. Ра Latte вижда шаблона по същия начин като вас. За разлика от Twig, разбира HTML и знае, че променливата се извежда като стойност на атрибут, който не е в кавички. Затова ги допълва. Когато нападателят вмъкне същото описание, резултатният код ще изглежда така: -```html +```latte
``` diff --git a/latte/cs/cookbook/grouping.texy b/latte/cs/cookbook/grouping.texy index bdb563efb5..34e1a2cff8 100644 --- a/latte/cs/cookbook/grouping.texy +++ b/latte/cs/cookbook/grouping.texy @@ -2,15 +2,17 @@ Všechno, co jste kdy chtěli vědět o seskupování *********************************************** .[perex] -Při práci s daty ve šablonách můžete často narazit na potřebu jejich seskupování nebo specifického zobrazení podle určitých kritérií. Latte pro tento účel nabízí hned několik silných nástrojů. +Při práci s daty ve šablonách často potřebujete položky seskupit, rozdělit do dávek nebo je procházet podle podmínky. Latte k tomu nabízí tři nástroje, z nichž každý se hodí na trochu jinou situaci. -Filtr a funkce `|group` umožňují efektivní seskupení dat podle zadaného kritéria, filtr `|batch` zase usnadňuje rozdělení dat do pevně daných dávek a značka `{iterateWhile}` poskytuje možnost složitějšího řízení průběhu cyklů s podmínkami. Každá z těchto značek nabízí specifické možnosti pro práci s daty, čímž se stávají nepostradatelnými nástroji pro dynamické a strukturované zobrazení informací v Latte šablonách. +Filtr `|group` a funkce `group()` seskupí položky podle zadaného kritéria, filtr `|batch` je rozdělí do dávek pevné velikosti a značka `{iterateWhile}` prochází data postupně a sama si určuje, kdy přerušit vnitřní smyčku. V textu si je postupně projdeme. Filtr a funkce `group` .{data-version:3.0.16} ============================================= -Představte si databázovou tabulku `items` s položkami rozdělenou do kategorií: +Nástroj lze používat ve dvou tvarech: jako filtr `$items|group: …` nebo jako funkci `group($items, …)`. Sémanticky jsou ekvivalentní, vyberte si podle čitelnosti. + +Představte si databázovou tabulku `items`, jejíž položky patří do různých kategorií: | id | categoryId | name |------------------ @@ -62,19 +64,17 @@ Pokud bychom ale chtěli, aby položky byly uspořádány do skupin podle katego {/foreach} ``` -Filtr lze v Latte použít i jako funkci, což nám dává alternativní syntaxi: `{foreach group($items, categoryId) ...}`. - -Chcete-li seskupovat položky podle složitějších kritérií, můžete v parametru filtru použít funkci. Například, seskupení položek podle délky názvu by vypadalo takto: +Chcete-li seskupovat položky podle složitějších kritérií, můžete v parametru filtru použít funkci. Klíčem každé skupiny pak bude návratová hodnota funkce — například při seskupení podle délky názvu to bude počet znaků: ```latte -{foreach ($items|group: fn($item) => strlen($item->name)) as $items} +{foreach ($items|group: fn($item) => strlen($item->name)) as $length => $group} ... {/foreach} ``` -Je důležité si uvědomit, že `$categoryItems` není běžné pole, ale objekt, který se chová jako iterátor. Pro přístup k první položce skupiny můžete použít funkci [`first()` |latte:functions#first]. +Je důležité si uvědomit, že každá skupina (tedy i `$categoryItems`) není běžné pole, ale objekt chovající se jako iterátor — nelze proto použít `$categoryItems[0]` ani `count($categoryItems)`. Pro přístup k první položce skupiny použijte funkci [`first()` |latte:functions#first]. -Tato flexibilita v seskupování dat činí `group` výjimečně užitečným nástrojem pro prezentaci dat v šablonách Latte. +Tato flexibilita činí `|group` výjimečně užitečným nástrojem pro prezentaci dat. Vnořené smyčky @@ -97,8 +97,8 @@ Představme si, že máme databázovou tabulku s dalším sloupcem `subcategoryI ``` -Spojení s Nette Database ------------------------- +Společně s Nette Database +------------------------- Pojďme si ukázat, jak efektivně využít seskupování dat v kombinaci s Nette Database. Předpokládejme, že pracujeme s tabulkou `items` z úvodního příkladu, která je prostřednictvím sloupce `categoryId` spojená s touto tabulkou `categories`: @@ -121,24 +121,24 @@ Data z tabulky `items` načteme pomocí Nette Database Explorer příkazem `$ite {/foreach} ``` -V tomto případě používáme filtr `|group` k seskupení podle propojeného řádku `$item->category`, nikoliv jen dle sloupce `categoryId`. Díky tomu v proměnné klíči přímo `ActiveRow` dané kategorie, což nám umožňuje přímo vypisovat její název pomocí `{$category->name}`. Toto je praktický příklad, jak může seskupování zpřehlednit šablony a usnadnit práci s daty. +V tomto případě používáme filtr `|group` k seskupení podle propojeného řádku `$item->category`, nikoliv jen dle sloupce `categoryId`. Díky tomu je v klíči (`$category`) rovnou `ActiveRow` dané kategorie, což nám umožňuje vypisovat její název pomocí `{$category->name}` a přistupovat k libovolnému dalšímu sloupci, aniž bychom museli dělat zvláštní dotaz na `categories`. Filtr `|batch` ============== -Filtr umožňuje rozdělit seznam prvků do skupin s předem určeným počtem prvků. Tento filtr je ideální pro situace, kdy chcete data prezentovat ve více menších skupinách, například pro lepší přehlednost nebo vizuální uspořádání na stránce. +Filtr rozdělí seznam prvků do dávek o pevně daném počtu. Hodí se třeba pro grid layout, sloupcové rozložení nebo jakékoli vizuální seskupení. -Představme si, že máme seznam položek a chceme je zobrazit v seznamech, kde každý obsahuje maximálně tři položky. Použití filtru `|batch` je v takovém případě velmi praktické: +Představme si, že chceme zobrazit položky v seznamech, kde každý obsahuje maximálně tři položky: ```latte -
{foreach ($items|batch: 3) as $batch} - {foreach $batch as $item} -
``` V tomto příkladu je seznam `$items` rozdělen do menších skupin, přičemž každá skupina (`$batch`) obsahuje až tři položky. Každá skupina je poté zobrazena v samostatném `- {$item->name}
- {/foreach} ++ {foreach $batch as $item} +
{/foreach} -- {$item->name}
+ {/foreach} +` seznamu. @@ -155,9 +155,9 @@ Pokud poslední skupina neobsahuje dostatek prvků k dosažení požadovaného p Značka `{iterateWhile}` ======================= -Stejné úkoly, jako jsme řešili s filtrem `|group`, si ukážeme s použitím značky `{iterateWhile}`. Hlavní rozdíl mezi oběma přístupy je v tom, že `group` nejprve zpracuje a seskupí všechna vstupní data, zatímco `{iterateWhile}` řídí průběhu cyklů s podmínkami, takže iterace probíhá postupně. +Stejné úkoly, jako jsme řešili s filtrem `|group`, si ukážeme s použitím značky `{iterateWhile}`. Hlavní rozdíl mezi oběma přístupy je v tom, že `|group` nejprve zpracuje a seskupí všechna vstupní data, zatímco `{iterateWhile}` řídí průběh cyklu pomocí podmínky a iterace probíhá postupně. -Nejprve vykreslíme tabulku s kategoriemi pomocí iterateWhile: +Nejprve vykreslíme tabulku s kategoriemi pomocí `{iterateWhile}`: ```latte {foreach $items as $item} @@ -169,7 +169,7 @@ Nejprve vykreslíme tabulku s kategoriemi pomocí iterateWhile: {/foreach} ``` -Zatímco `{foreach}` označuje vnější část cyklu, tedy vykreslování seznamů pro každou kategorii, tak značka `{iterateWhile}` označuje vnitřní část, tedy jednotlivé položky. Podmínka v koncové značce říká, že opakování bude probíhat do té doby, dokud aktuální i následující prvek patří do stejné kategorie (`$iterator->nextValue` je [následující položka |/tags#iterator]). +Zatímco `{foreach}` označuje vnější část cyklu, tedy vykreslování seznamů pro každou kategorii, tak značka `{iterateWhile}` označuje vnitřní část, tedy jednotlivé položky. Podmínka v koncové značce říká, že opakování bude probíhat do té doby, dokud aktuální i následující prvek patří do stejné kategorie (`$iterator->nextValue` je [následující položka |/tags#iterator]; u posledního prvku je `null` a porovnání pak vyjde false, takže vnitřní cyklus přirozeně skončí). Kdyby podmínka byla splněná vždy, tak se ve vnitřním cyklu vykreslí všechny prvky: @@ -196,11 +196,11 @@ Výsledek bude vypadat takto:
``` -K čemu je takové použití iterateWhile dobré? Když bude tabulka prázdná a nebude obsahovat žádné prvky, nevypíše se prázdné ``. +K čemu je takové použití `{iterateWhile}` dobré? Tím, že je `
` uvnitř vnějšího `{foreach}`, se při prázdném vstupu nevykreslí vůbec nic — žádný osamělý `
``` +(Prázdné ``. Bez `{iterateWhile}` byste totéž museli ošetřit `{if}` před otevřením tagu nebo přes `{foreachelse}`. Pokud uvedeme podmínku v otevírací značce `{iterateWhile}`, tak se chování změní: podmínka (a přechod na další prvek) se vykoná už na začátku vnitřního cyklu, nikoliv na konci. Tedy zatímco do `{iterateWhile}` bez podmínky se vstoupí vždy, do `{iterateWhile $cond}` jen při splnění podmínky `$cond`. A zároveň se s tím do `$item` zapíše následující prvek. -Což se hodí například v situaci, kdy budeme chtít první prvek v každé kategorii vykreslit jiným způsobem, například takto: +Hodí se to v situaci, kdy chceme první prvek v každé kategorii vykreslit jiným způsobem než ty ostatní, například takto: ```latte
Apple
@@ -219,6 +219,8 @@ Což se hodí například v situaci, kdy budeme chtít první prvek v každé ka` u kategorie PHP je tu jen ilustrací mechaniky — v reálném kódu byste vykreslení `
` ošetřili `{if}`.) + Původní kód upravíme tak, že nejprve vykreslíme první položku a poté ve vnitřním cyklu `{iterateWhile}` vykreslíme další položky ze stejné kategorie: ```latte @@ -232,9 +234,9 @@ Původní kód upravíme tak, že nejprve vykreslíme první položku a poté ve {/foreach} ``` -V rámci jednoho cyklu můžeme vytvářet více vnitřních smyček a dokonce je zanořovat. Takto by se daly seskupovat třeba podkategorie atd. +V rámci jednoho cyklu můžeme vytvářet více vnitřních smyček a dokonce je zanořovat. Tímto způsobem lze seskupovat na více úrovních současně — třeba podkategorie pod kategoriemi. -Dejme tomu, že v tabulce bude ještě další sloupec `subcategoryId` a kromě toho, že každá kategorie bude v samostatném `
``` @@ -1003,7 +1003,7 @@ Zástupné symboly `PrintContext::format()` - **`%args`**: Argument musí být `Expression\ArrayNode`. Vypíše položky pole formátované jako argumenty pro volání funkce nebo metody (oddělené čárkami, zpracovává pojmenované argumenty, pokud jsou přítomny). - `$argsNode = new ArrayNode([...]);` - `$context->format('myFunc(%args);', $argsNode)` -> `myFunc(1, name: 'Joe');` -- **`%line`**: Argument musí být objekt `Position` (obvykle `$this->position`). Vkládá PHP komentář `/* line X */` indikující číslo řádku zdroje. +- **`%line`**: Argument musí být objekt `Position` (nebo `Range`, obvykle `$this->position`). Vkládá PHP komentář `/* line X */` indikující číslo řádku zdroje. - `$context->format('echo "Hi" %line;', $this->position)` -> `echo "Hi" /* line 42:1 */;` - **`%escape(...)`**: Generuje PHP kód, který *za běhu* escapuje vnitřní výraz pomocí aktuálních kontextově uvědomělých pravidel escapování. - `$context->format('echo %escape(%node);', $variableNode)` @@ -1023,7 +1023,7 @@ Zatímco `parseExpression()`, `parseArguments()`, atd., pokrývají mnoho příp ```php setTempDirectory('/path/to/tempdir'); +$latte->setCacheDirectory('/path/to/tempdir'); $params = [ /* proměnné šablony */ ]; // or $params = new TemplateParameters(/* ... */); @@ -58,6 +58,20 @@ $latte->setAutoRefresh(false); Při nasazení na produkčním serveru může prvotní vygenerování cache, zejména u rozsáhlejších aplikací, pochopitelně chviličku trvat. Latte má vestavěnou prevenci před "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. Jde o situaci, kdy se sejde větší počet souběžných požadavků, které spustí Latte, a protože cache ještě neexistuje, začaly by ji všechny generovat současně. Což by neúměrně zatížilo server. Latte je chytré a při více souběžných požadavcích generuje cache pouze první vlákno, ostatní čekají a následně ji využíjí. +Způsoby rozšíření Latte +======================= + +Latte můžete přizpůsobit hned několika způsoby, od jednoduchých pomocníků až po vlastní jazykové konstrukce. Podrobně se jim věnuje stránka [rozšiřujeme Latte |extending-latte], zde je stručný přehled: + +- **[Vlastní filtry |custom-filters]:** pro formátování nebo transformaci dat ve výstupu šablony (např. `{$var|myFilter}`). +- **[Vlastní funkce |custom-functions]:** pro vlastní logiku, kterou voláte ve výrazech šablony (např. `{myFunction($arg)}`). +- **[Vlastní tagy |custom-tags]:** pro zcela nové jazykové konstrukce (`{mytag}...{/mytag}` nebo `n:mytag`). +- **[Kompilační průchody |compiler-passes]:** funkce, které upravují AST šablony mezi parsováním a generováním PHP kódu (například pro optimalizace nebo bezpečnostní kontroly). +- **[Vlastní loadery |loaders]:** pro změnu způsobu, jakým Latte vyhledává a načítá soubory šablon. + +Pokud chcete svá rozšíření znovu použít v jiných projektech nebo je sdílet s ostatními, zabalte je do třídy [Latte Extension |extending-latte#Latte Extension]. + + Parametry jako třída ==================== @@ -184,18 +198,18 @@ Ve striktním režimu parsování Latte kontroluje, zda nechybí uzavírací HTM ```php $latte = new Latte\Engine; -$latte->setStrictParsing(); +$latte->setFeature(Latte\Feature::StrictParsing); ``` Generování šablon s hlavičkou `declare(strict_types=1)` zapnete takto: ```php $latte = new Latte\Engine; -$latte->setStrictTypes(); +$latte->setFeature(Latte\Feature::StrictTypes); ``` .[note] -Od verze Latte 3.1 jsou strict types povoleny ve výchozím nastavení. Můžete je deaktivovat pomocí `$latte->setStrictTypes(false)`. +Od verze Latte 3.1 jsou strict types povoleny ve výchozím nastavení. Můžete je deaktivovat pomocí `$latte->setFeature(Latte\Feature::StrictTypes, false)`. Migrační varování .{data-version:3.1} @@ -216,6 +230,70 @@ Pokud je toto zapnuto, Latte kontroluje vykreslované atributy a vyvolá uživat Jakmile jsou všechna varování vyřešena, vypněte varování o migraci a **odstraňte všechny** filtry `|accept` ze svých šablon, protože již nejsou potřeba. +Scopované proměnné cyklu .{data-version:3.1.3} +============================================== + +Ve výchozím nastavení zůstávají proměnné definované v cyklu `{foreach}` (jako `$key` a `$value`) dostupné i po jeho skončení – stejně jako v samotném PHP. To může vést k nechtěnému přepsání proměnných, pokud má proměnná cyklu stejný název jako existující proměnná šablony. + +Funkce `ScopedLoopVariables` omezí platnost proměnných na tělo cyklu. Po jeho skončení se obnoví původní hodnota proměnné (pokud existovala), nebo se proměnná odstraní: + +```php +$latte = new Latte\Engine; +$latte->setFeature(Latte\Feature::ScopedLoopVariables); +``` + +Příklad rozdílu: + +```latte +{var $item = 'original'} +{foreach [1, 2] as $item}{$item}, {/foreach} +{$item} +``` + +Bez `ScopedLoopVariables`: vypíše `1, 2, 2` (proměnná je přepsána) +Se `ScopedLoopVariables`: vypíše `1, 2, original` (proměnná je obnovena) + +Funguje to i s destrukturováním, např. `{foreach $array as [$a, $b]}`. + +.[note] +Proměnné cyklu používající reference (`{foreach $array as &$value}`) nebo přiřazení do vlastností (`{foreach $array as $obj->prop}`) nejsou scopovány, protože by to narušilo jejich účel. + + +Automatické odsazení (Dedent) .{toc: Dedent}{data-version:3.1.3} +================================================================ + +Při používání párových značek jako `{if}`, `{foreach}` nebo `{block}` se vnořený obsah často odsazuje pro lepší čitelnost. Toto odsazení se ale ve výchozím nastavení přenáší do vygenerovaného výstupu. Funkce `Dedent` ho automaticky odstraní, takže výstup zůstane čistý bez ohledu na úroveň zanoření v šabloně: + +```php +$latte = new Latte\Engine; +$latte->setFeature(Latte\Feature::Dedent); +``` + +Příklad: + +```latte +{if true} + Hello + World +{/if} +``` + +Bez `Dedent` by výstup obsahoval odsazení (`\tHello\n\tWorld\n`). S `Dedent` se odsazení odstraní a výstupem je `Hello\nWorld\n`. + +Hlubší odsazení uvnitř bloku zůstává zachováno relativně k základnímu odsazení: + +```latte +{if true} + Hello + Indented +{/if} +``` + +Výstup: `Hello\n\tIndented\n`. + +Odsazení v bloku musí být konzistentní (buď tabulátory, nebo mezery). Pokud se mísí, Latte vyhodí výjimku `Inconsistent indentation`. + + Překládání v šablonách .{toc: TranslatorExtension} ================================================== diff --git a/latte/cs/filters.texy b/latte/cs/filters.texy index bcce55ae3e..5a53e646d0 100644 --- a/latte/cs/filters.texy +++ b/latte/cs/filters.texy @@ -10,6 +10,9 @@ V šablonách můžeme používat funkce, které pomáhají upravit nebo přefor | `breakLines` | [Před konce řádku přidá HTML odřádkování |#breakLines] | `bytes` | [formátuje velikost v bajtech |#bytes] | `clamp` | [ohraničí hodnotu do daného rozsahu |#clamp] +| `column` | [extrahuje jeden sloupec z pole |#column] +| `commas` | [spojí pole čárkami |#commas] +| `limit` | [omezí délku pole, řetězce nebo iterátoru |#limit] | `dataStream` | [konverze pro Data URI protokol |#dataStream] | `date` | [formátuje datum a čas |#date] | `explode` | [rozdělí řetězec na pole podle oddělovače |#explode] @@ -259,6 +262,50 @@ Ohraničí hodnotu do daného inkluzivního rozsahu min a max. Existuje také jako [funkce |functions#clamp]. +column(string|int|null $columnKey, string|int|null $indexKey=null) .[filter]{data-version:3.1.3} +------------------------------------------------------------------------------------------------ +Vrátí z vícerozměrného pole hodnoty jednoho sloupce `$columnKey` jako nové pole. Lze použít i na pole objektů pro získání hodnot vlastností. + +```latte +{var $users = [ + [id: 30, name: 'John', age: 30], + [id: 32, name: 'Jane', age: 25], + [id: 33, age: 35], +]} + +{$users|column: 'name'} +{* vrátí ['John', 'Jane'] *} + +{$users|column: 'name', 'id'} +{* vrátí [30 => 'John', 32 => 'Jane'] *} +``` + +Pokud předáte `null` jako klíč sloupce, přeindexuje pole podle `$indexKey`. + + +commas(?string $lastGlue=null) .[filter]{data-version:3.1.3} +------------------------------------------------------------ +Spojí prvky pole čárkou a mezerou (`', '`). Jde o pohodlnou zkratku pro běžný výpis položek v čitelné podobě. + +```latte +{var $items = ['jablka', 'pomeranče', 'banány']} +{$items|commas} +{* vypíše 'jablka, pomeranče, banány' *} +``` + +Lze zadat i vlastní oddělovač pro poslední dvojici položek: + +```latte +{$items|commas: ' a '} +{* vypíše 'jablka, pomeranče a banány' *} + +{=['PHP', 'JavaScript', 'Python']|commas: ', nebo '} +{* vypíše 'PHP, JavaScript, nebo Python' *} +``` + +Viz také [#implode]. + + dataStream(string $mimetype=detect) .[filter] --------------------------------------------- Konvertuje obsah do data URI scheme. Pomocí něj lze do HTML nebo CSS vkládat obrázky bez nutnosti linkovat externí soubory. @@ -407,6 +454,8 @@ Můžete také použít alias `join`: {=[1, 2, 3]|join} {* vypíše '123' *} ``` +Viz také [#commas], [#explode]. + indent(int $level=1, string $char="\t") .[filter] ------------------------------------------------- @@ -637,19 +686,21 @@ Pamatujte, že skutečný vzhled čísel se může lišit podle nastavení země padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- -Doplní řetězec do určité délky jiným řetězcem zleva. +Doplní řetězec nebo číslo do určité délky jiným řetězcem zleva. ```latte {='hello'|padLeft: 10, '123'} {* vypíše '12312hello' *} +{=123|padLeft: 5, '0'} {* vypíše '00123' *} ``` padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ -Doplní řetězec do určité délky jiným řetězcem zprava. +Doplní řetězec nebo číslo do určité délky jiným řetězcem zprava. ```latte {='hello'|padRight: 10, '123'} {* vypíše 'hello12312' *} +{=123|padRight: 5, '0'} {* vypíše '12300' *} ``` @@ -747,14 +798,14 @@ Viz také [#ceil], [#floor]. slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] ------------------------------------------------------------------------ -Extrahuje část pole nebo řetězce. +Extrahuje část pole, řetězce nebo iterátoru. ```latte {='hello'|slice: 1, 2} {* vypíše 'el' *} {=['a', 'b', 'c']|slice: 1, 2} {* vypíše ['b', 'c'] *} ``` -Filtr funguje jako funkce PHP `array_slice` pro pole nebo `mb_substr` pro řetězce s fallbackem na funkci `iconv_substr` v režimu UTF‑8. +Filtr funguje jako funkce PHP `array_slice` pro pole nebo `mb_substr` pro řetězce. Pro iterátory vrací generátor – prvky se čtou z původního zdroje jeden po druhém a po dosažení limitu se čtení zastaví. Celý iterátor se do paměti nenačítá. Pokud je start kladný, posloupnost začné posunutá o tento počet od začátku pole/řetezce. Pokud je záporný posloupnost začné posunutá o tolik od konce. @@ -762,6 +813,21 @@ Pokud je zadaný parametr length a je kladný, posloupnost bude obsahovat tolik Ve výchozím nastavení filtr změní pořadí a resetuje celočíselného klíče pole. Toto chování lze změnit nastavením preserveKeys na true. Řetězcové klíče jsou vždy zachovány, bez ohledu na tento parametr. +Viz také [#limit]. + + +limit(int $length) .[filter]{data-version:3.1.3} +------------------------------------------------ +Omezí délku pole, řetězce nebo iterátoru. U polí a iterátorů zachovává klíče. U řetězců respektuje UTF-8. + +```latte +{foreach ($items|limit: 5) as $item} + ... +{/foreach} + +{$text|limit: 100} +``` + sort(?Closure $comparison, string|int|\Closure|null $by=null, string|int|\Closure|bool $byKey=false) .[filter] -------------------------------------------------------------------------------------------------------------- diff --git a/latte/cs/recipes.texy b/latte/cs/recipes.texy index a11d147a0e..172a222a08 100644 --- a/latte/cs/recipes.texy +++ b/latte/cs/recipes.texy @@ -7,7 +7,7 @@ Editory a IDE Pište šablony v editoru nebo IDE, který má podporu pro Latte. Bude to mnohem příjemnější. -- PhpStorm: nainstalujte v `Settings > Plugins > Marketplace` [plugin Latte|https://plugins.jetbrains.com/plugin/7457-latte] +- PhpStorm: nainstalujte v `Settings > Plugins > Marketplace` [plugin Latte|https://plugins.jetbrains.com/plugin/24218-latte-support] - VS Code: nainstalujte [Nette Latte + Neon|https://marketplace.visualstudio.com/items?itemName=Kasik96.latte], [Nette Latte templates|https://marketplace.visualstudio.com/items?itemName=smuuf.latte-lang] nebo nejnovější [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode] plugin - NetBeans IDE: nativní podpora Latte je součástí instalace - Sublime Text 3: v Package Control najděte a nainstalujte balíček `Nette` a zvolte Latte ve `View > Syntax` diff --git a/latte/cs/safety-first.texy b/latte/cs/safety-first.texy index e76142ec39..b718215a1b 100644 --- a/latte/cs/safety-first.texy +++ b/latte/cs/safety-first.texy @@ -33,7 +33,7 @@ echo '`, každá každý podkategorie samostatném `
`: +Dejme tomu, že v tabulce bude ještě další sloupec `subcategoryId` a kromě toho, že každá kategorie bude v samostatném `
`, každá podkategorie bude v samostatném `
`: ```latte {foreach $items as $item} diff --git a/latte/cs/custom-filters.texy b/latte/cs/custom-filters.texy index 7f7cab0c0c..299d9c1aae 100644 --- a/latte/cs/custom-filters.texy +++ b/latte/cs/custom-filters.texy @@ -84,7 +84,7 @@ Registrace pomocí rozšíření Pro lepší organizaci, zejména při vytváření znovupoužitelných sad filtrů nebo jejich sdílení jako balíčky, je doporučeným způsobem registrovat je v rámci [rozšíření Latte |extending-latte#Latte Extension]: ```php -namespace App\Latte; +namespace App\Templating; use Latte\Extension; @@ -111,7 +111,7 @@ class MyLatteExtension extends Extension // Registrace $latte = new Latte\Engine; -$latte->addExtension(new App\Latte\MyLatteExtension); +$latte->addExtension(new MyLatteExtension); ``` Tento přístup udrží logiku vašeho filtru zapouzdřenou a registraci jednoduchou. diff --git a/latte/cs/custom-functions.texy b/latte/cs/custom-functions.texy index 8a1e70f188..1bd18de0e3 100644 --- a/latte/cs/custom-functions.texy +++ b/latte/cs/custom-functions.texy @@ -67,7 +67,7 @@ Registrace pomocí rozšíření Pro lepší organizaci a znovupoužitelnost registrujte funkce v rámci [Latte rozšíření |extending-latte#Latte Extension]. Tento přístup je doporučen pro složitější aplikace nebo sdílené knihovny. ```php -namespace App\Latte; +namespace App\Templating; use Latte\Extension; use Nette\Security\Authorizator; @@ -95,7 +95,7 @@ class MyLatteExtension extends Extension } // Registrace (předpokládáme, že $container obsahuje DIC) -$extension = $container->getByType(App\Latte\MyLatteExtension::class); +$extension = $container->getByType(MyLatteExtension::class); $latte = new Latte\Engine; $latte->addExtension($extension); ``` diff --git a/latte/cs/custom-tags.texy b/latte/cs/custom-tags.texy index 7d41e7799d..4c35f4c796 100644 --- a/latte/cs/custom-tags.texy +++ b/latte/cs/custom-tags.texy @@ -117,7 +117,7 @@ Vytvořte soubor (např. `DatetimeNode.php`) a definujte třídu: ```php format()`, která sestavuje výsledný řetězec PHP kódu pro kompilovanou šablonu. První argument, `'echo date('Y-m-d H:i:s') %line;'`, je maska, do které jsou doplněny následující parametry. Zástupný symbol `%line` říká metodě `format()`, aby použila druhý argument, kterým je `$this->position`, a vložila komentář jako `/* line 15 */`, který propojuje vygenerovaný PHP kód zpět na původní řádek šablony, což je klíčové pro ladění. -Vlastnost `$this->position` je zděděna ze základní třídy `Node` a je automaticky nastavena parserem Latte. Obsahuje objekt [api:Latte\Compiler\Position], který indikuje, kde byl tag nalezen ve zdrojovém souboru `.latte`. +Vlastnost `$this->position` je zděděna ze základní třídy `Node` a je automaticky nastavena parserem Latte. Obsahuje objekt [api:Latte\Compiler\Range] (potomek třídy `Position` rozšířený o vlastnost `length` v bajtech), který udává, kde se tag v souboru `.latte` nachází. U párových tagů pokrývá rozsah od otevíracího po uzavírací tag a potomci `StatementNode` navíc nabízejí pole `$this->tagRanges` s objekty `Range` pro každý dílčí tag (otevírací, mezilehlé jako `{else}`/`{case}` i uzavírací). Metoda `getIterator()` je zásadní pro kompilační průchody. Musí poskytovat všechny dětské uzly, ale náš jednoduchý `DatetimeNode` aktuálně nemá žádné argumenty ani obsah, tedy žádné dětské uzly. Nicméně metoda musí stále existovat a být generátorem, tj. klíčové slovo `yield` musí být nějakým způsobem přítomno v těle metody. @@ -173,7 +173,7 @@ Nakonec informujme Latte o novém tagu. Vytvořte [třídu rozšíření |extend ```php addExtension(new App\Latte\MyLatteExtension); +$latte->addExtension(new App\Templating\MyLatteExtension); ``` Vytvořte šablonu: @@ -255,7 +255,7 @@ S tímto pochopením upravme metodu `create()` v `DatetimeNode` tak, aby parsova ```php addExtension(new App\Latte\MyLatteExtension($isDev)); +$latte->addExtension(new MyLatteExtension($isDev)); ``` A jeho použití v šabloně: @@ -555,7 +555,7 @@ Upravme `DebugNode::create()` tak, aby očekával `{else}`: ```php Smazat
Výsledky vyhledávání pro ' . $search . '
'; Útočník může do vyhledávacího políčka a potažmo do proměnné `$search` zapsat libovolný řetězec, tedy i HTML kód jako ``. Protože výstup není nijak ošetřen, stane se součástí zobrazené stránky: -```html +```latteVýsledky vyhledávání pro
``` @@ -59,7 +59,7 @@ echo ''; Útočníkovi stačí jako popisek vložit šikovně sestavený řetězec `" onload="alert('Hacked!')` a když vypsání nebude ošetřeno, výsledný kód bude vypadat takto: -```html +```latte
``` @@ -91,7 +91,7 @@ Kontextově sensitivní escapování Co se přesně myslí slovem kontext? Jde o místo v dokumentu s vlastními pravidly pro ošetřování vypisovaných dat. Odvíjí se od typu dokumentu (HTML, XML, CSS, JavaScript, plain text, ...) a může se lišit v jeho konkrétních částech. Například v HTML dokumentu je takových míst (kontextů), kde platí velmi odlišná pravidla, celá řada. Možná budete překvapeni, kolik jich je. Tady máme první čtveřici: -```html +```latte
#text
@@ -108,7 +108,7 @@ Zajímavé je to uvnitř HTML komentářů. Tady se totiž k escapování nepou Kontexty se také mohou vrstvit, k čemuž dochází, když vložíme JavaScript nebo CSS do HTML. To lze udělat dvěma odlišnými způsoby, elementem a atributem: -```html +```latte
@@ -132,7 +132,7 @@ Mějme řetězec `Rock'n'Roll`. Pokud jej budete vypisovat v HTML textu, zrovna v tomhle případě netřeba dělat žádné záměny, protože řetězec neobsahuje žádný znak se speciálním významem. Jiná situace nastane, pokud jej vypíšete uvnitř HTML atributu uvozeného do jednoduchých uvozovek. V takovém případě je potřeba escapovat uvozovky na HTML entity: -```html +```latte ``` @@ -152,13 +152,13 @@ alert('Rock\'n\'Roll'); Pokud tento kód vložíme do HTML dokumentu pomocí ` alert('Rock\'n\'Roll'); ``` Pokud bychom jej však chtěli vložit do HTML atributu, musíme ještě escapovat uvozovky na HTML entity: -```html +```latte ``` @@ -170,7 +170,7 @@ https://example.org/?a=Jazz&b=Rock%27n%27Roll A když tento řetězec vypíšeme v atributu, ještě aplikujeme escapování podle tohoto kontextu a nahradíme `&` za `&`: -```html +```latte ``` @@ -314,7 +314,7 @@ Všimněte si, že okolo hodnot atributů nejsou uvozovky. Kodér na ně mohl za Útočník jako popisek obrázku vloží šikovně sestavený řetězec `foo onload=alert('Hacked!')`. Už víme, že Twig nemůže poznat, jestli se proměnná vypisuje v toku HTML textu, uvnitř atributu, HTML komentáře, atd., zkrátka nerozlišuje kontexty. A jen mechanicky převádí znaky `< > & ' "` na HTML entity. Takže výsledný kód bude vypadat takto: -```html +```latte
``` @@ -330,7 +330,7 @@ Nyní se podíváme, jak si se stejnou šablonou poradí Latte: Latte vidí šablonu stejně jako vy. Na rozdíl od Twigu chápe HTML a ví, že proměnná se vypisuje jako hodnota atributu, který není v uvozovkách. Proto je doplní. Když útočník vloží stejný popisek, výsledný kód bude vypadat takto: -```html +```latte
``` diff --git a/latte/cs/syntax.texy b/latte/cs/syntax.texy index 1e21ab6dcf..39f1c621de 100644 --- a/latte/cs/syntax.texy +++ b/latte/cs/syntax.texy @@ -218,6 +218,41 @@ Uvnitř značek fungují PHP komentáře: ``` +Řízení bílých znaků +=================== + +Latte zachází s bílými znaky inteligentně. Kód můžete volně odsazovat pro čitelnost a výstup zůstane čistý. Když se tag objeví na řádku sám, celý řádek (odsazení i konec řádku) se z výstupu odstraní: + +```latte +
+ {foreach $items as $item} +
+``` + +Vypíše: + +```latte +- {$item}
+ {/foreach} ++
+``` + +A co když tag není na řádku sám, ale je tam i další obsah? Bílé znaky před tagem pak patří *dovnitř* tagu: + +```latte +- foo
+- bar
++ {if $foo}hello{/if} ++``` + +Odsazení je tedy fakticky uvnitř `{if}`: pokud je `$foo` false, nevypíše se nic – ani odsazení, ani prázdný řádek. Pokud je `$foo` true, výstup přirozeně obsahuje odsazení. Prostě pište přehledně odsazené šablony a výstup bude vždy čistý. + +Pro ještě čistší výstup lze aktivovat funkci [Dedent |develop#Dedent], která odstraní i odsazení vzniklé zanořením v párových značkách jako `{if}` nebo `{foreach}`. + + Syntaktický cukr ================ diff --git a/latte/cs/tags.texy b/latte/cs/tags.texy index 598b2b73dd..cb8b03378a 100644 --- a/latte/cs/tags.texy +++ b/latte/cs/tags.texy @@ -137,7 +137,7 @@ Jako výraz můžete zapsat cokoliv, co znáte z PHP. Nemusíte se zkrátka uči ```latte -{='0' . ($num ?? $num * 3) . ', ' . PHP_VERSION} +{='0' . ($num ?? $num * 3) . ', ' . \PHP_VERSION} ``` Prosím, nehledejte v předchozím příkladu žádný smysl, ale kdybyste tam nějaký našli, napište nám :-) diff --git a/latte/de/custom-tags.texy b/latte/de/custom-tags.texy index 483b5f5820..89089b627d 100644 --- a/latte/de/custom-tags.texy +++ b/latte/de/custom-tags.texy @@ -923,7 +923,7 @@ Jetzt können Sie `n:confirm` auf Links, Schaltflächen oder Formularelementen v Generiertes HTML: -```html +```latte Löschen ``` diff --git a/latte/de/safety-first.texy b/latte/de/safety-first.texy index 2f5b0697a1..bdc0cb85fa 100644 --- a/latte/de/safety-first.texy +++ b/latte/de/safety-first.texy @@ -33,7 +33,7 @@ echo 'Suchergebnisse für ' . $search . '
'; Ein Angreifer kann in das Suchfeld und somit in die Variable `$search` eine beliebige Zeichenkette eingeben, also auch HTML-Code wie ``. Da die Ausgabe nicht bereinigt wird, wird sie Teil der angezeigten Seite: -```html +```latteSuchergebnisse für
``` @@ -59,7 +59,7 @@ echo ''; Dem Angreifer genügt es, als Beschreibung eine geschickt konstruierte Zeichenkette `" onload="alert('Gehackt!')` einzufügen, und wenn die Ausgabe nicht bereinigt wird, sieht der resultierende Code so aus: -```html +```latte
``` @@ -91,7 +91,7 @@ Kontextsensitives Escaping Was genau ist mit dem Wort Kontext gemeint? Es handelt sich um eine Stelle im Dokument mit eigenen Regeln für die Bereinigung ausgegebener Daten. Sie hängt vom Dokumenttyp ab (HTML, XML, CSS, JavaScript, Plain Text, ...) und kann sich in seinen spezifischen Teilen unterscheiden. Beispielsweise gibt es in einem HTML-Dokument eine ganze Reihe solcher Stellen (Kontexte), an denen sehr unterschiedliche Regeln gelten. Vielleicht werden Sie überrascht sein, wie viele es sind. Hier sind die ersten vier: -```html +```latte
#text
@@ -108,7 +108,7 @@ Interessant ist es innerhalb von HTML-Kommentaren. Hier wird nämlich kein Escap Kontexte können sich auch verschachteln, was passiert, wenn wir JavaScript oder CSS in HTML einbetten. Dies kann auf zwei verschiedene Arten geschehen, mit einem Element und einem Attribut: -```html +```latte
@@ -132,7 +132,7 @@ Nehmen wir die Zeichenkette `Rock'n'Roll`. Wenn Sie sie im HTML-Text ausgeben, müssen in diesem Fall keine Ersetzungen vorgenommen werden, da die Zeichenkette kein Zeichen mit besonderer Bedeutung enthält. Eine andere Situation ergibt sich, wenn Sie sie innerhalb eines HTML-Attributs ausgeben, das in einfache Anführungszeichen eingeschlossen ist. In diesem Fall müssen die Anführungszeichen in HTML-Entitäten escapet werden: -```html +```latte ``` @@ -152,13 +152,13 @@ alert('Rock\'n\'Roll'); Wenn wir diesen Code mit ` alert('Rock\'n\'Roll'); ``` Wenn wir ihn jedoch in ein HTML-Attribut einfügen wollten, müssten wir die Anführungszeichen noch in HTML-Entitäten escapen: -```html +```latte ``` @@ -170,7 +170,7 @@ https://example.org/?a=Jazz&b=Rock%27n%27Roll Und wenn wir diese Zeichenkette in einem Attribut ausgeben, wenden wir noch das Escaping gemäß diesem Kontext an und ersetzen `&` durch `&`: -```html +```latte ``` @@ -314,7 +314,7 @@ Beachten Sie, dass um die Attributwerte keine Anführungszeichen stehen. Der Pro Ein Angreifer fügt als Bildbeschreibung eine geschickt konstruierte Zeichenkette `foo onload=alert('Gehackt!')` ein. Wir wissen bereits, dass Twig nicht erkennen kann, ob die Variable im Fluss des HTML-Textes, innerhalb eines Attributs, eines HTML-Kommentars usw. ausgegeben wird, kurz gesagt, es unterscheidet keine Kontexte. Und konvertiert nur mechanisch die Zeichen `< > & ' "` in HTML-Entitäten. Der resultierende Code sieht also so aus: -```html +```latte
``` @@ -330,7 +330,7 @@ Sehen wir uns nun an, wie Latte mit demselben Template umgeht: Latte sieht das Template genauso wie Sie. Im Gegensatz zu Twig versteht es HTML und weiß, dass die Variable als Wert eines Attributs ausgegeben wird, das nicht in Anführungszeichen steht. Deshalb ergänzt es sie. Wenn ein Angreifer dieselbe Beschreibung einfügt, sieht der resultierende Code so aus: -```html +```latte
``` diff --git a/latte/el/custom-tags.texy b/latte/el/custom-tags.texy index cf69c32588..3c041dd29d 100644 --- a/latte/el/custom-tags.texy +++ b/latte/el/custom-tags.texy @@ -923,7 +923,7 @@ class MyLatteExtension extends Extension Παραγόμενο HTML: -```html +```latte Διαγραφή ``` diff --git a/latte/el/safety-first.texy b/latte/el/safety-first.texy index bb4b7c09d7..65985510ef 100644 --- a/latte/el/safety-first.texy +++ b/latte/el/safety-first.texy @@ -33,7 +33,7 @@ echo '
Αποτελέσματα αναζήτησης για ' . $search . Ένας εισβολέας μπορεί να γράψει στο πεδίο αναζήτησης και κατ' επέκταση στη μεταβλητή `$search` οποιοδήποτε string, δηλαδή και κώδικα HTML όπως ``. Επειδή η έξοδος δεν επεξεργάζεται με κανέναν τρόπο, γίνεται μέρος της εμφανιζόμενης σελίδας: -```html +```latte
Αποτελέσματα αναζήτησης για
``` @@ -59,7 +59,7 @@ echo ''; Αρκεί ο εισβολέας να εισάγει ως λεζάντα ένα έξυπνα κατασκευασμένο string `" onload="alert('Hacked!')` και αν η εκτύπωση δεν επεξεργαστεί, ο προκύπτων κώδικας θα μοιάζει ως εξής: -```html +```latte
``` @@ -91,7 +91,7 @@ Context-Aware Escaping Τι ακριβώς εννοούμε με τη λέξη context; Πρόκειται για ένα μέρος στο έγγραφο με τους δικούς του κανόνες για την επεξεργασία των εκτυπωμένων δεδομένων. Εξαρτάται από τον τύπο του εγγράφου (HTML, XML, CSS, JavaScript, plain text, ...) και μπορεί να διαφέρει σε συγκεκριμένα μέρη του. Για παράδειγμα, σε ένα έγγραφο HTML, υπάρχουν πολλά τέτοια μέρη (contexts) όπου ισχύουν πολύ διαφορετικοί κανόνες. Ίσως εκπλαγείτε πόσα είναι. Εδώ έχουμε την πρώτη τετράδα: -```html +```latte
#κείμενο
@@ -108,7 +108,7 @@ Context-Aware Escaping Τα contexts μπορούν επίσης να στρωματοποιηθούν, κάτι που συμβαίνει όταν ενσωματώνουμε JavaScript ή CSS σε HTML. Αυτό μπορεί να γίνει με δύο διαφορετικούς τρόπους, με στοιχείο και με attribute: -```html +```latte
@@ -132,7 +132,7 @@ Context-Aware Escaping Αν το εκτυπώσετε σε κείμενο HTML, σε αυτή τη συγκεκριμένη περίπτωση δεν χρειάζεται να κάνετε καμία αντικατάσταση, επειδή το string δεν περιέχει κανέναν χαρακτήρα με ειδική σημασία. Η κατάσταση αλλάζει αν το εκτυπώσετε μέσα σε ένα attribute HTML που περικλείεται σε απλά εισαγωγικά. Σε αυτή την περίπτωση, πρέπει να κάνετε escape τα εισαγωγικά σε οντότητες HTML: -```html +```latte ``` @@ -152,13 +152,13 @@ alert('Rock\'n\'Roll'); Αν εισάγουμε αυτόν τον κώδικα σε ένα έγγραφο HTML χρησιμοποιώντας το ` alert('Rock\'n\'Roll'); ``` Αν όμως θέλαμε να το εισάγουμε σε ένα attribute HTML, πρέπει ακόμα να κάνουμε escape τα εισαγωγικά σε οντότητες HTML: -```html +```latte ``` @@ -170,7 +170,7 @@ https://example.org/?a=Jazz&b=Rock%27n%27Roll Και όταν εκτυπώνουμε αυτό το string σε ένα attribute, εφαρμόζουμε επιπλέον το escaping σύμφωνα με αυτό το context και αντικαθιστούμε το `&` με `&`: -```html +```latte ``` @@ -314,7 +314,7 @@ Latte εναντίον απλοϊκών συστημάτων Ένας εισβολέας εισάγει ως λεζάντα της εικόνας ένα έξυπνα κατασκευασμένο string `foo onload=alert('Hacked!')`. Γνωρίζουμε ήδη ότι το Twig δεν μπορεί να αναγνωρίσει αν η μεταβλητή εκτυπώνεται στη ροή του κειμένου HTML, μέσα σε ένα attribute, σε ένα σχόλιο HTML κ.λπ., με λίγα λόγια δεν διακρίνει τα contexts. Και απλώς μετατρέπει μηχανικά τους χαρακτήρες `< > & ' "` σε οντότητες HTML. Έτσι, ο προκύπτων κώδικας θα μοιάζει ως εξής: -```html +```latte
``` @@ -330,7 +330,7 @@ Latte εναντίον απλοϊκών συστημάτων Το Latte βλέπει το πρότυπο όπως εσείς. Σε αντίθεση με το Twig, καταλαβαίνει HTML και ξέρει ότι η μεταβλητή εκτυπώνεται ως τιμή ενός attribute που δεν βρίσκεται σε εισαγωγικά. Γι' αυτό τα συμπληρώνει. Όταν ο εισβολέας εισάγει την ίδια λεζάντα, ο προκύπτων κώδικας θα μοιάζει ως εξής: -```html +```latte
``` diff --git a/latte/en/cookbook/grouping.texy b/latte/en/cookbook/grouping.texy index 7a8458bb0c..1d4b7bf049 100644 --- a/latte/en/cookbook/grouping.texy +++ b/latte/en/cookbook/grouping.texy @@ -2,15 +2,17 @@ Everything You Always Wanted to Know About Grouping *************************************************** .[perex] -When working with data in templates, you often encounter the need to group them or display them specifically according to certain criteria. Latte offers several powerful tools for this purpose. +When working with data in templates, you often need to group items, split them into batches, or iterate through them based on a condition. Latte offers three tools for this, each suited to a slightly different situation. -The filter and function `|group` allow for efficient data grouping based on specified criteria, the `|batch` filter facilitates splitting data into fixed-size batches, and the `{iterateWhile}` tag provides the ability to control loop progression with more complex conditions. Each of these features offers specific options for working with data, making them indispensable tools for dynamic and structured display of information in Latte templates. +The `|group` filter groups items by a given criterion, the `|batch` filter splits them into batches of fixed size, and the `{iterateWhile}` tag iterates through data step by step and decides for itself when to break the inner loop. We'll walk through them one by one. Filter and Function `group` .{data-version:3.0.16} ================================================== -Imagine a database table `items` with items divided into categories: +The tool can be used in two forms: as a filter `$items|group: …` or as a function `group($items, …)`. Semantically they are equivalent — choose based on readability. + +Imagine a database table `items` whose items belong to various categories: | id | categoryId | name |-----|------------|-------- @@ -62,19 +64,17 @@ This task can be easily and elegantly solved using `|group`. We specify `categor {/foreach} ``` -The filter can also be used as a function in Latte, providing an alternative syntax: `{foreach group($items, categoryId) ...}`. - -If you want to group items based on more complex criteria, you can use a function in the filter parameter. For example, grouping items by the length of their name would look like this: +If you want to group items based on more complex criteria, you can use a function in the filter parameter. The key of each group will then be the return value of the function — for example, when grouping by name length, it will be the number of characters: ```latte -{foreach ($items|group: fn($item) => strlen($item->name)) as $items} +{foreach ($items|group: fn($item) => strlen($item->name)) as $length => $group} ... {/foreach} ``` -It’s important to note that `$categoryItems` is not a regular array, but an object that behaves like an iterator. To access the first item in the group, you can use the [`first()` |latte:functions#first] function. +It's important to note that each group (including `$categoryItems`) is not a regular array, but an object that behaves like an iterator — so you cannot use `$categoryItems[0]` or `count($categoryItems)`. To access the first item in the group, use the [`first()` |latte:functions#first] function. -This flexibility in data grouping makes `group` an exceptionally useful tool for presenting data in Latte templates. +This flexibility makes `|group` an exceptionally useful tool for presenting data. Nested Loops @@ -97,8 +97,8 @@ Let's imagine our database table has an additional column `subcategoryId`, defin ``` -Integration with Nette Database -------------------------------- +Together with Nette Database +---------------------------- Let's demonstrate how to effectively use data grouping in combination with Nette Database. Assume we are working with the `items` table from the introductory example, connected via the `categoryId` column to this `categories` table: @@ -121,24 +121,24 @@ We load data from the `items` table using Nette Database Explorer with the comma {/foreach} ``` -In this case, we use the `|group` filter to group by the related row object `$item->category`, not just the `categoryId` column. As a result, the key variable `$category` directly holds the `ActiveRow` object for that category, allowing us to display its name directly using `{$category->name}`. This is a practical example of how grouping can simplify templates and facilitate working with related data. +In this case, we use the `|group` filter to group by the related row `$item->category`, not just the `categoryId` column. As a result, the key (`$category`) directly holds the `ActiveRow` object for that category, allowing us to display its name using `{$category->name}` and access any other column without making a separate query to `categories`. Filter `|batch` =============== -The `|batch` filter allows you to divide a list of items into groups (batches) with a predetermined number of items. This filter is ideal for situations where you want to present data in several smaller chunks, for example, for better clarity or visual layout on the page. +The filter splits a list of items into batches of a fixed size. It's handy for grid layouts, column arrangements, or any kind of visual grouping. -Imagine we have a list of items and want to display them in lists, where each list contains a maximum of three items. Using the `|batch` filter is very practical in such a case: +Imagine we want to display items in lists where each list contains a maximum of three items: ```latte -
{foreach ($items|batch: 3) as $batch} - {foreach $batch as $item} -
``` In this example, the `$items` list is divided into smaller groups, where each group (`$batch`) contains up to three items. Each batch is then displayed in a separate `- {$item->name}
- {/foreach} ++ {foreach $batch as $item} +
{/foreach} -- {$item->name}
+ {/foreach} +` list. @@ -155,9 +155,9 @@ If the last group does not contain enough elements to reach the desired number, Tag `{iterateWhile}` ==================== -We will demonstrate the same tasks addressed with the `|group` filter using the `{iterateWhile}` tag. The main difference between the two approaches is that `|group` first processes and groups all input data, whereas `{iterateWhile}` controls the loop's progression based on conditions, allowing iteration to proceed sequentially. +We will demonstrate the same tasks addressed with the `|group` filter using the `{iterateWhile}` tag. The main difference between the two approaches is that `|group` first processes and groups all input data, whereas `{iterateWhile}` controls the loop's progression via a condition and iteration proceeds sequentially. -First, let's render the table with categories using `iterateWhile`: +First, let's render the table with categories using `{iterateWhile}`: ```latte {foreach $items as $item} @@ -169,7 +169,7 @@ First, let's render the table with categories using `iterateWhile`: {/foreach} ``` -While `{foreach}` marks the outer part of the cycle, i.e., drawing lists for each category, the `{iterateWhile}` tag marks the inner part, i.e., individual items. The condition in the end tag says that repetition will continue as long as the current and next element belong to the same category (`$iterator->nextValue` is the [next item |/tags#iterator]). +While `{foreach}` marks the outer part of the cycle, i.e., drawing lists for each category, the `{iterateWhile}` tag marks the inner part, i.e., individual items. The condition in the end tag says that repetition will continue as long as the current and next element belong to the same category (`$iterator->nextValue` is the [next item |/tags#iterator]; for the last element it is `null` and the comparison then evaluates to false, so the inner loop naturally ends). If the condition were always true, all elements would be rendered within the first `
``` @@ -1003,7 +1003,7 @@ We've frequently used `PrintContext::format()` to generate PHP code in the `prin - **`%args`**: Argument must be an `Expression\ArrayNode`. It prints the array items formatted as arguments for a function or method call (comma-separated, handling named arguments if present). - `$argsNode = new ArrayNode([...]);` - `$context->format('myFunc(%args);', $argsNode)` -> `myFunc(1, name: 'Joe');` -- **`%line`**: Argument must be a `Position` object (usually `$this->position`). It inserts a PHP comment `/* line X */` indicating the source line number. +- **`%line`**: Argument must be a `Position` (or `Range`) object (usually `$this->position`). It inserts a PHP comment `/* line X */` indicating the source line number. - `$context->format('echo "Hi" %line;', $this->position)` -> `echo "Hi" /* line 42:1 */;` - **`%escape(...)`**: It generates PHP code that, *at runtime*, will escape the inner expression using the current context-aware escaping rules. - `$context->format('echo %escape(%node);', $variableNode)` @@ -1023,7 +1023,7 @@ While `parseExpression()`, `parseArguments()`, etc., cover many cases, sometimes ```php setTempDirectory('/path/to/tempdir'); +$latte->setCacheDirectory('/path/to/tempdir'); $params = [ /* template variables */ ]; // or $params = new TemplateParameters(/* ... */); @@ -58,6 +58,20 @@ $latte->setAutoRefresh(false); When deployed on a production server, the initial cache generation, especially for larger applications, can understandably take a while. Latte has built-in prevention against "cache stampede":https://en.wikipedia.org/wiki/Cache_stampede. This is a situation where server receives a large number of concurrent requests and because Latte's cache does not yet exist, they would all generate it at the same time. Which spikes CPU. Latte is smart, and when there are multiple concurrent requests, only the first thread generates the cache, the others wait and then use it. +Ways to Extend Latte +==================== + +Latte can be customized in several ways, from simple helpers to entirely new language constructs. The page [extending Latte |extending-latte] covers them in detail; here is a quick overview: + +- **[Custom Filters|custom-filters]:** for formatting or transforming data in the template output (e.g., `{$var|myFilter}`). +- **[Custom Functions|custom-functions]:** for custom logic you call within template expressions (e.g., `{myFunction($arg)}`). +- **[Custom Tags|custom-tags]:** for entirely new language constructs (`{mytag}...{/mytag}` or `n:mytag`). +- **[Compiler Passes|compiler-passes]:** functions that modify the template's AST between parsing and PHP code generation (for example, optimizations or security checks). +- **[Custom Loaders|loaders]:** for changing how Latte locates and loads template files. + +If you want to reuse your extensions across projects or share them with others, bundle them into a [Latte Extension |extending-latte#Latte Extension] class. + + Parameters as a Class ===================== @@ -184,18 +198,18 @@ In strict parsing mode, Latte checks for missing closing HTML tags and also disa ```php $latte = new Latte\Engine; -$latte->setStrictParsing(); +$latte->setFeature(Latte\Feature::StrictParsing); ``` To generate templates with the `declare(strict_types=1)` header, do the following: ```php $latte = new Latte\Engine; -$latte->setStrictTypes(); +$latte->setFeature(Latte\Feature::StrictTypes); ``` .[note] -Since Latte 3.1, strict types are enabled by default. You can disable them with `$latte->setStrictTypes(false)`. +Since Latte 3.1, strict types are enabled by default. You can disable them with `$latte->setFeature(Latte\Feature::StrictTypes, false)`. Migration Warnings .{data-version:3.1} @@ -216,6 +230,70 @@ When enabled, Latte checks rendered attributes and triggers a user warning (`E_U Once all warnings are resolved, disable migration warnings and **remove all** `|accept` filters from your templates, as they are no longer needed. +Scoped Loop Variables .{data-version:3.1.3} +=========================================== + +By default, variables defined in a `{foreach}` loop (like `$key` and `$value`) remain accessible after the loop ends – just like in PHP itself. This can lead to unintended variable overwrites when a loop variable has the same name as an existing template variable. + +The `ScopedLoopVariables` feature limits the scope of loop variables to the loop body. After the loop ends, the original variable value is restored (if it existed before), or the variable is unset: + +```php +$latte = new Latte\Engine; +$latte->setFeature(Latte\Feature::ScopedLoopVariables); +``` + +Example of the difference: + +```latte +{var $item = 'original'} +{foreach [1, 2] as $item}{$item}, {/foreach} +{$item} +``` + +Without `ScopedLoopVariables`: outputs `1, 2, 2` (variable is overwritten) +With `ScopedLoopVariables`: outputs `1, 2, original` (variable is restored) + +This also works with destructuring syntax, e.g. `{foreach $array as [$a, $b]}`. + +.[note] +Loop variables using references (`{foreach $array as &$value}`) or property assignments (`{foreach $array as $obj->prop}`) are not scoped, as this would break their intended purpose. + + +Automatic Dedentation .{toc: Dedent}{data-version:3.1.3} +======================================================== + +When using paired tags like `{if}`, `{foreach}`, or `{block}`, you often indent the nested content for readability. However, this indentation is included in the generated output by default. The `Dedent` feature automatically removes it, so the output stays clean regardless of how deeply you nest your Latte tags: + +```php +$latte = new Latte\Engine; +$latte->setFeature(Latte\Feature::Dedent); +``` + +Example: + +```latte +{if true} + Hello + World +{/if} +``` + +Without `Dedent`, the output would include the indentation (`\tHello\n\tWorld\n`). With `Dedent`, the indentation is stripped and the output is `Hello\nWorld\n`. + +Deeper indentation within a block is preserved relative to the base indentation: + +```latte +{if true} + Hello + Indented +{/if} +``` + +Output: `Hello\n\tIndented\n`. + +Indentation within a block must be consistent (either tabs or spaces). If they are mixed, Latte throws an `Inconsistent indentation` exception. + + Translation in Templates .{toc: TranslatorExtension} ==================================================== diff --git a/latte/en/filters.texy b/latte/en/filters.texy index c87f8ffceb..813daf354d 100644 --- a/latte/en/filters.texy +++ b/latte/en/filters.texy @@ -10,6 +10,9 @@ In templates, we can use functions that help modify or reformat data into its fi | `breakLines` | [Inserts HTML line breaks before all newlines |#breakLines] | `bytes` | [formats size in bytes |#bytes] | `clamp` | [clamps a value to the given range |#clamp] +| `column` | [extracts a single column from an array |#column] +| `commas` | [joins an array with commas |#commas] +| `limit` | [limits the length of an array, string, or iterator |#limit] | `dataStream` | [Data URI protocol conversion |#dataStream] | `date` | [formats the date and time |#date] | `explode` | [splits a string into an array by a delimiter |#explode] @@ -259,6 +262,50 @@ Clamps a value to the given inclusive range of min and max. Also exists as a [function |functions#clamp]. +column(string|int|null $columnKey, string|int|null $indexKey=null) .[filter]{data-version:3.1.3} +------------------------------------------------------------------------------------------------ +Returns the values of a single column `$columnKey` from a multidimensional array as a new array. Can also be used on arrays of objects to extract property values. + +```latte +{var $users = [ + [id: 30, name: 'John', age: 30], + [id: 32, name: 'Jane', age: 25], + [id: 33, age: 35], +]} + +{$users|column: 'name'} +{* returns ['John', 'Jane'] *} + +{$users|column: 'name', 'id'} +{* returns [30 => 'John', 32 => 'Jane'] *} +``` + +If you pass `null` as the column key, it will reindex the array according to `$indexKey`. + + +commas(?string $lastGlue=null) .[filter]{data-version:3.1.3} +------------------------------------------------------------ +Joins array elements with a comma and space (`', '`). A convenient shortcut for listing items in a human-readable format. + +```latte +{var $items = ['apples', 'oranges', 'bananas']} +{$items|commas} +{* outputs 'apples, oranges, bananas' *} +``` + +You can also provide a custom separator for the last pair of items: + +```latte +{$items|commas: ' and '} +{* outputs 'apples, oranges and bananas' *} + +{=['PHP', 'JavaScript', 'Python']|commas: ', or '} +{* outputs 'PHP, JavaScript, or Python' *} +``` + +See also [#implode]. + + dataStream(string $mimetype='detect') .[filter] ----------------------------------------------- Converts content to the data URI scheme. This allows embedding images into HTML or CSS without needing to link external files. @@ -407,6 +454,8 @@ You can also use the alias `join`: {=[1, 2, 3]|join} {* outputs '123' *} ``` +See also [#commas], [#explode]. + indent(int $level=1, string $char="\t") .[filter] ------------------------------------------------- @@ -637,19 +686,21 @@ Remember that the actual appearance of numbers may vary depending on the country padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- -Pads a string to a certain length with another string from the left. +Pads a string or number to a certain length with another string from the left. ```latte {='hello'|padLeft: 10, '123'} {* outputs '12312hello' *} +{=123|padLeft: 5, '0'} {* outputs '00123' *} ``` padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ -Pads a string to a certain length with another string from the right. +Pads a string or number to a certain length with another string from the right. ```latte {='hello'|padRight: 10, '123'} {* outputs 'hello12312' *} +{=123|padRight: 5, '0'} {* outputs '12300' *} ``` @@ -747,14 +798,14 @@ See also [#ceil], [#floor]. slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] ------------------------------------------------------------------------ -Extracts a slice of an array or a string. +Extracts a slice of an array, string, or iterator. ```latte {='hello'|slice: 1, 2} {* outputs 'el' *} {=['a', 'b', 'c']|slice: 1, 2} {* outputs ['b', 'c'] *} ``` -The filter works like the PHP function `array_slice` for arrays or `mb_substr` for strings, with a fallback to the `iconv_substr` function in UTF‑8 mode. +The filter works like the PHP function `array_slice` for arrays or `mb_substr` for strings. For iterators, it returns a generator – elements are consumed from the source one by one and reading stops once the limit is reached. The entire iterator is never loaded into memory. If `start` is non-negative, the sequence will start at that offset from the beginning of the array/string. If `start` is negative, the sequence will start that far from the end. @@ -762,6 +813,21 @@ If `length` is given and is positive, then the sequence will have up to that man By default, the filter reorders and resets the integer array keys. This behavior can be changed by setting `preserveKeys` to true. String keys are always preserved, regardless of this parameter. +See also [#limit]. + + +limit(int $length) .[filter]{data-version:3.1.3} +------------------------------------------------ +Limits the length of an array, string, or iterator. For arrays and iterators, keys are preserved. For strings, it respects UTF-8. + +```latte +{foreach ($items|limit: 5) as $item} + ... +{/foreach} + +{$text|limit: 100} +``` + sort(?Closure $comparison, string|int|\Closure|null $by=null, string|int|\Closure|bool $byKey=false) .[filter] -------------------------------------------------------------------------------------------------------------- diff --git a/latte/en/recipes.texy b/latte/en/recipes.texy index 37896b3ee5..7547e5b989 100644 --- a/latte/en/recipes.texy +++ b/latte/en/recipes.texy @@ -7,7 +7,7 @@ Editors and IDE Write templates in an editor or IDE that supports Latte. It will be much more pleasant. -- PhpStorm: install the [Latte plugin|https://plugins.jetbrains.com/plugin/7457-latte] in `Settings > Plugins > Marketplace` +- PhpStorm: install the [Latte plugin|https://plugins.jetbrains.com/plugin/24218-latte-support] in `Settings > Plugins > Marketplace` - VS Code: install [Nette Latte + Neon|https://marketplace.visualstudio.com/items?itemName=Kasik96.latte], [Nette Latte templates|https://marketplace.visualstudio.com/items?itemName=smuuf.latte-lang] or the latest [Nette for VS Code |https://marketplace.visualstudio.com/items?itemName=franken-ui.nette-for-vscode] plugin - NetBeans IDE: native support for Latte is included in the installation - Sublime Text 3: find and install the `Nette` package in Package Control and choose Latte in `View > Syntax` diff --git a/latte/en/safety-first.texy b/latte/en/safety-first.texy index 4e2c9480f4..004656ca94 100644 --- a/latte/en/safety-first.texy +++ b/latte/en/safety-first.texy @@ -33,7 +33,7 @@ echo '`: @@ -196,11 +196,11 @@ The result would look like this:
``` -What's the benefit of using `iterateWhile` like this? If the `$items` array is empty, no empty `` tags will be printed. +What's the benefit of using `{iterateWhile}` like this? Because the `
` is inside the outer `{foreach}`, nothing is rendered at all when the input is empty — no lone `
``` +(The empty ``. Without `{iterateWhile}` you would have to handle the same case with an `{if}` before opening the tag or via `{foreachelse}`. If we specify the condition in the opening `{iterateWhile}` tag, the behavior changes: the condition (and transition to the next element) is performed at the beginning of the inner cycle, not at the end. Thus, while you always enter `{iterateWhile}` without conditions, you enter `{iterateWhile $cond}` only when the condition `$cond` is met. And at the same time, the next element is written into `$item`. -This is useful, for instance, when you want to render the first item in each category differently, like this: +This is useful in situations where we want to render the first item in each category differently from the others, for example like this: ```latte
Apple
@@ -219,6 +219,8 @@ This is useful, for instance, when you want to render the first item in each cat` for the PHP category is just an illustration of the mechanics — in real code you would handle the `
` rendering with an `{if}`.) + We modify the original code to first render the item as a heading, and then use the inner `{iterateWhile}` loop to render subsequent items from the same category as list items: ```latte @@ -232,9 +234,9 @@ We modify the original code to first render the item as a heading, and then use {/foreach} ``` -Within a single `{foreach}` loop, you can create multiple inner `{iterateWhile}` loops and even nest them. This could be used, for example, to group subcategories. +Within a single loop, we can create multiple inner loops and even nest them. This way you can group on multiple levels at once — for example, subcategories under categories. -Let's assume the table has another column `subcategoryId`, and besides having each category in a separate `
`, each subcategory should be in a separate `
`: +Let's assume the table has another column `subcategoryId`, and besides each category being in a separate `
`, each subcategory will be in a separate `
`: ```latte {foreach $items as $item} diff --git a/latte/en/custom-filters.texy b/latte/en/custom-filters.texy index 9fc3ea58b6..db6d77bf8d 100644 --- a/latte/en/custom-filters.texy +++ b/latte/en/custom-filters.texy @@ -84,7 +84,7 @@ Registration via Extension For better organization, especially when creating reusable sets of filters or sharing them as packages, the recommended way is to register them within a [Latte Extension |extending-latte#Latte Extension]: ```php -namespace App\Latte; +namespace App\Templating; use Latte\Extension; @@ -111,7 +111,7 @@ class MyLatteExtension extends Extension // Registration $latte = new Latte\Engine; -$latte->addExtension(new App\Latte\MyLatteExtension); +$latte->addExtension(new MyLatteExtension); ``` This approach keeps your filter logic encapsulated and makes registration straightforward. diff --git a/latte/en/custom-functions.texy b/latte/en/custom-functions.texy index 4c4a4d423d..eeef7d8008 100644 --- a/latte/en/custom-functions.texy +++ b/latte/en/custom-functions.texy @@ -67,7 +67,7 @@ Registration via Extension For better organization and reusability, register functions within a [Latte Extension |extending-latte#Latte Extension]. This is the recommended approach for non-trivial applications or shared libraries. ```php -namespace App\Latte; +namespace App\Templating; use Latte\Extension; use Nette\Security\Authorizator; @@ -95,7 +95,7 @@ class MyLatteExtension extends Extension } // Registration (assuming $container holds the DIC) -$extension = $container->getByType(App\Latte\MyLatteExtension::class); +$extension = $container->getByType(MyLatteExtension::class); $latte = new Latte\Engine; $latte->addExtension($extension); ``` diff --git a/latte/en/custom-tags.texy b/latte/en/custom-tags.texy index b427e74a27..b162bc8e10 100644 --- a/latte/en/custom-tags.texy +++ b/latte/en/custom-tags.texy @@ -117,7 +117,7 @@ Create a file (e.g., `DatetimeNode.php`) and define the class: ```php format()` method, which assembles the resulting PHP code string for the compiled template. The first argument, `'echo date('Y-m-d H:i:s') %line;'`, is the mask into which the subsequent parameters are substituted. The `%line` placeholder tells the `format()` method to take the second following argument, which is `$this->position`, and inserts a comment like `/* line 15 */` that links the generated PHP code back to the original template line, which is crucial for debugging. -Property `$this->position` is inherited from the base `Node` class, and is automatically set by Latte's parser. It holds a [api:Latte\Compiler\Position] object indicating where the tag was found in the source `.latte` file. +Property `$this->position` is inherited from the base `Node` class, and is automatically set by Latte's parser. It holds a [api:Latte\Compiler\Range] object (a subclass of `Position` extended with a `length` in bytes) indicating where the tag is located in the source `.latte` file. For paired tags the range spans from the opening to the closing tag, and `StatementNode` descendants additionally expose `$this->tagRanges` listing the `Range` of every constituent tag (opening, intermediate like `{else}`/`{case}`, and closing). The `getIterator()` method is vital for compiler passes. It must yield all child nodes, but our simple `DatetimeNode` currently has no arguments or content, thus no child nodes. However, the method must still exist and be a generator, i.e. the `yield` keyword must be somehow present in the method body. @@ -173,7 +173,7 @@ Finally, tell Latte about the new tag. Create an [Extension class |extending-lat ```php addExtension(new App\Latte\MyLatteExtension); +$latte->addExtension(new App\Templating\MyLatteExtension); ``` Create template: @@ -255,7 +255,7 @@ With that understanding, let's modify the `create()` method in `DatetimeNode` to ```php addExtension(new App\Latte\MyLatteExtension($isDev)); +$latte->addExtension(new MyLatteExtension($isDev)); ``` And use it in a template: @@ -555,7 +555,7 @@ Let's modify `DebugNode::create()` to expect `{else}`: ```php Delete
Search results for ' . $search . '
'; An attacker can enter any string into the search box, and thus into the `$search` variable, including HTML code like ``. Since the output is not sanitized, it becomes part of the displayed page: -```html +```latteSearch results for
``` @@ -59,7 +59,7 @@ echo ''; An attacker simply needs to insert a cleverly crafted string `" onload="alert('Hacked!')` as the caption, and if the output is not sanitized, the resulting code will look like this: -```html +```latte
``` @@ -91,7 +91,7 @@ Context-Aware Escaping What exactly is meant by the word context? It's a location within the document with its own rules for handling the data being printed. It depends on the document type (HTML, XML, CSS, JavaScript, plain text, ...) and can differ in specific parts. For example, in an HTML document, there are many places (contexts) where very different rules apply. You might be surprised how many there are. Here are the first four: -```html +```latte
#text
@@ -108,7 +108,7 @@ It gets interesting inside HTML comments. Here, HTML entities are not used for e Contexts can also be layered, which occurs when we embed JavaScript or CSS into HTML. This can be done in two different ways, using an element or an attribute: -```html +```latte
@@ -132,7 +132,7 @@ Let's take the string `Rock'n'Roll`. If you print it in HTML text, in this particular case, no replacement is needed because the string does not contain any characters with special meaning. The situation changes if you print it inside an HTML attribute enclosed in single quotes. In that case, you need to escape the quotes into HTML entities: -```html +```latte ``` @@ -152,13 +152,13 @@ alert('Rock\'n\'Roll'); If we insert this code into an HTML document using ` alert('Rock\'n\'Roll'); ``` However, if we wanted to insert it into an HTML attribute, we still need to escape the quotes into HTML entities: -```html +```latte ``` @@ -170,7 +170,7 @@ https://example.org/?a=Jazz&b=Rock%27n%27Roll And when we print this string in an attribute, we still apply escaping according to this context and replace `&` with `&`: -```html +```latte ``` @@ -314,7 +314,7 @@ Notice that there are no quotes around the attribute values. The coder might hav An attacker inserts a cleverly crafted string `foo onload=alert('Hacked!')` as the image caption. We already know that Twig cannot determine whether a variable is being printed in the HTML text flow, inside an attribute, an HTML comment, etc.; in short, it does not distinguish contexts. And it just mechanically converts the characters `< > & ' "` into HTML entities. So the resulting code will look like this: -```html +```latte
``` @@ -330,7 +330,7 @@ Now let's see how Latte handles the same template: Latte sees the template the same way you do. Unlike Twig, it understands HTML and knows that the variable is being printed as the value of an attribute that is not enclosed in quotes. Therefore, it adds them. When an attacker inserts the same caption, the resulting code will look like this: -```html +```latte
``` diff --git a/latte/en/syntax.texy b/latte/en/syntax.texy index ef926e75be..aa7e274f5f 100644 --- a/latte/en/syntax.texy +++ b/latte/en/syntax.texy @@ -218,6 +218,41 @@ PHP comments work inside tags: ``` +Whitespace Control +================== + +Latte handles whitespace intelligently. You can freely indent your code for readability, and the output stays clean. When a tag appears alone on a line, the entire line (indentation and newline) is removed from the output: + +```latte +
+ {foreach $items as $item} +
+``` + +Outputs: + +```latte +- {$item}
+ {/foreach} ++
+``` + +What if a tag isn't alone on a line, but appears alongside other content? The whitespace before the tag then belongs *inside* the tag: + +```latte +- foo
+- bar
++ {if $foo}hello{/if} ++``` + +The indentation is effectively inside `{if}`: when `$foo` is false, nothing is output – not even the indentation or a blank line. When `$foo` is true, the output naturally includes the indentation. You simply write well-structured templates and the output is always clean. + +For even cleaner output, you can enable the [Dedent |develop#Dedent] feature, which also removes indentation caused by nesting within paired tags like `{if}` or `{foreach}`. + + Syntactic Sugar =============== diff --git a/latte/en/tags.texy b/latte/en/tags.texy index cfbc94ae6f..ffb90c69c1 100644 --- a/latte/en/tags.texy +++ b/latte/en/tags.texy @@ -137,7 +137,7 @@ You can write anything you know from PHP as an expression. You simply don't have ```latte -{='0' . ($num ?? $num * 3) . ', ' . PHP_VERSION} +{='0' . ($num ?? $num * 3) . ', ' . \PHP_VERSION} ``` Please don't look for any meaning in the previous example, but if you find one, let us know :-) diff --git a/latte/es/custom-tags.texy b/latte/es/custom-tags.texy index f32e309910..fbb0646b5b 100644 --- a/latte/es/custom-tags.texy +++ b/latte/es/custom-tags.texy @@ -923,7 +923,7 @@ Ahora puede usar `n:confirm` en enlaces, botones o elementos de formulario: HTML generado: -```html +```latte Eliminar ``` diff --git a/latte/es/safety-first.texy b/latte/es/safety-first.texy index 437dd39aac..6fe173ea9c 100644 --- a/latte/es/safety-first.texy +++ b/latte/es/safety-first.texy @@ -33,7 +33,7 @@ echo 'Resultados de la búsqueda para ' . $search . '
'; Un atacante puede escribir en el campo de búsqueda y, por extensión, en la variable `$search` cualquier cadena, incluido código HTML como ``. Dado que la salida no está saneada de ninguna manera, se convierte en parte de la página mostrada: -```html +```latteResultados de la búsqueda para
``` @@ -59,7 +59,7 @@ echo ''; Al atacante le basta con insertar como descripción una cadena hábilmente construida `" onload="alert('Hacked!')` y si la impresión no está saneada, el código resultante se verá así: -```html +```latte
``` @@ -91,7 +91,7 @@ Escape sensible al contexto ¿Qué se entiende exactamente por la palabra contexto? Es un lugar en el documento con sus propias reglas para el saneamiento de los datos impresos. Depende del tipo de documento (HTML, XML, CSS, JavaScript, texto plano, ...) y puede diferir en sus partes específicas. Por ejemplo, en un documento HTML hay muchos lugares (contextos) donde se aplican reglas muy diferentes. Quizás se sorprenda de cuántos hay. Aquí tenemos los primeros cuatro: -```html +```latte
#texto
@@ -108,7 +108,7 @@ Es interesante dentro de los comentarios HTML. Aquí, el escape no se realiza ut Los contextos también pueden anidarse, lo que ocurre cuando insertamos JavaScript o CSS en HTML. Esto se puede hacer de dos maneras diferentes, con un elemento y con un atributo: -```html +```latte
@@ -132,7 +132,7 @@ Tomemos la cadena `Rock'n'Roll`. Si la imprime en texto HTML, en este caso particular no es necesario realizar ningún reemplazo, porque la cadena no contiene ningún carácter con significado especial. La situación cambia si la imprime dentro de un atributo HTML delimitado por comillas simples. En ese caso, es necesario escapar las comillas a entidades HTML: -```html +```latte ``` @@ -152,13 +152,13 @@ alert('Rock\'n\'Roll'); Si insertamos este código en un documento HTML usando ` alert('Rock\'n\'Roll'); ``` Sin embargo, si quisiéramos insertarlo en un atributo HTML, aún debemos escapar las comillas a entidades HTML: -```html +```latte ``` @@ -170,7 +170,7 @@ https://example.org/?a=Jazz&b=Rock%27n%27Roll Y cuando imprimimos esta cadena en un atributo, aún aplicamos el escape según este contexto y reemplazamos `&` por `&`: -```html +```latte ``` @@ -314,7 +314,7 @@ Observe que no hay comillas alrededor de los valores de los atributos. El codifi Un atacante inserta como descripción de la imagen una cadena hábilmente construida `foo onload=alert('Hacked!')`. Ya sabemos que Twig no puede saber si la variable se imprime en el flujo de texto HTML, dentro de un atributo, comentario HTML, etc., en resumen, no distingue contextos. Y solo convierte mecánicamente los caracteres `< > & ' "` en entidades HTML. Así que el código resultante se verá así: -```html +```latte
``` @@ -330,7 +330,7 @@ Ahora veamos cómo Latte maneja la misma plantilla: Latte ve la plantilla igual que usted. A diferencia de Twig, entiende HTML y sabe que la variable se imprime como el valor de un atributo que no está entre comillas. Por eso las añade. Cuando un atacante inserta la misma descripción, el código resultante se verá así: -```html +```latte
``` diff --git a/latte/fr/custom-tags.texy b/latte/fr/custom-tags.texy index 29c14a08a8..9983a44429 100644 --- a/latte/fr/custom-tags.texy +++ b/latte/fr/custom-tags.texy @@ -923,7 +923,7 @@ Vous pouvez maintenant utiliser `n:confirm` sur des liens, des boutons ou des é HTML généré : -```html +```latte Supprimer ``` diff --git a/latte/fr/safety-first.texy b/latte/fr/safety-first.texy index e4b3f5a93a..d9476e4305 100644 --- a/latte/fr/safety-first.texy +++ b/latte/fr/safety-first.texy @@ -33,7 +33,7 @@ echo '
Résultats de la recherche pour ' . $search . '
'; Un attaquant peut entrer dans le champ de recherche et donc dans la variable `$search` n'importe quelle chaîne, y compris du code HTML comme ``. Comme la sortie n'est pas traitée, elle devient partie intégrante de la page affichée : -```html +```latteRésultats de la recherche pour
``` @@ -59,7 +59,7 @@ echo ''; Il suffit à l'attaquant d'insérer comme légende une chaîne habilement construite `" onload="alert('Piraté !')` et si l'affichage n'est pas traité, le code résultant ressemblera à ceci : -```html +```latte
``` @@ -91,7 +91,7 @@ Cependant, XSS ne concerne pas seulement l'affichage des données dans les templ Que signifie exactement le mot contexte ? C'est un endroit dans le document avec ses propres règles pour traiter les données affichées. Il dépend du type de document (HTML, XML, CSS, JavaScript, texte brut, ...) et peut varier dans ses parties spécifiques. Par exemple, dans un document HTML, il existe de nombreux endroits (contextes) où des règles très différentes s'appliquent. Vous serez peut-être surpris de leur nombre. Voici les quatre premiers : -```html +```latte
#texte
@@ -108,7 +108,7 @@ C'est intéressant à l'intérieur des commentaires HTML. Ici, l'échappement n' Les contextes peuvent également être imbriqués, ce qui se produit lorsque nous insérons du JavaScript ou du CSS dans du HTML. Cela peut être fait de deux manières différentes, par élément et par attribut : -```html +```latte
@@ -132,7 +132,7 @@ Prenons la chaîne `Rock'n'Roll`. Si vous l'affichez dans du texte HTML, dans ce cas précis, il n'est pas nécessaire de faire de remplacements, car la chaîne ne contient aucun caractère ayant une signification spéciale. La situation change si vous l'affichez à l'intérieur d'un attribut HTML entouré de guillemets simples. Dans ce cas, il faut échapper les guillemets en entités HTML : -```html +```latte ``` @@ -152,13 +152,13 @@ alert('Rock\'n\'Roll'); Si nous insérons ce code dans un document HTML à l'aide de ` alert('Rock\'n\'Roll'); ``` Cependant, si nous voulions l'insérer dans un attribut HTML, nous devrions encore échapper les guillemets en entités HTML : -```html +```latte ``` @@ -170,7 +170,7 @@ https://example.org/?a=Jazz&b=Rock%27n%27Roll Et lorsque nous affichons cette chaîne dans un attribut, nous appliquons encore l'échappement selon ce contexte et remplaçons `&` par `&`: -```html +```latte ``` @@ -314,7 +314,7 @@ Notez qu'il n'y a pas de guillemets autour des valeurs des attributs. Le codeur L'attaquant insère comme légende de l'image une chaîne habilement construite `foo onload=alert('Piraté !')`. Nous savons déjà que Twig ne peut pas savoir si la variable est affichée dans le flux de texte HTML, à l'intérieur d'un attribut, d'un commentaire HTML, etc., bref, il ne distingue pas les contextes. Et il ne fait que convertir mécaniquement les caractères `< > & ' "` en entités HTML. Le code résultant ressemblera donc à ceci : -```html +```latte
``` @@ -330,7 +330,7 @@ Voyons maintenant comment Latte gère le même template : Latte voit le template de la même manière que vous. Contrairement à Twig, il comprend HTML et sait que la variable est affichée comme valeur d'un attribut qui n'est pas entre guillemets. C'est pourquoi il les ajoute. Lorsque l'attaquant insère la même légende, le code résultant ressemblera à ceci : -```html +```latte
``` diff --git a/latte/hu/custom-tags.texy b/latte/hu/custom-tags.texy index f8e2946305..6b7f70dbe5 100644 --- a/latte/hu/custom-tags.texy +++ b/latte/hu/custom-tags.texy @@ -923,7 +923,7 @@ Most már használhatja az `n:confirm`-ot linkeken, gombokon vagy űrlap elemeke Generált HTML: -```html +```latte Törlés ``` diff --git a/latte/hu/safety-first.texy b/latte/hu/safety-first.texy index a07f648fe6..a1b2547fa0 100644 --- a/latte/hu/safety-first.texy +++ b/latte/hu/safety-first.texy @@ -33,7 +33,7 @@ echo '
Keresési eredmények erre: ' . $search . '
'; A támadó a keresőmezőbe és ezáltal a `$search` változóba bármilyen stringet beírhat, tehát HTML kódot is, mint ``. Mivel a kimenet nincs semmilyen módon kezelve, a megjelenített oldal részévé válik: -```html +```latteKeresési eredmények erre:
``` @@ -59,7 +59,7 @@ echo ''; A támadónak elég leírásként egy ügyesen összeállított `" onload="alert('Hacked!')` stringet beilleszteni, és ha a kiírás nincs kezelve, az eredményül kapott kód így fog kinézni: -```html +```latte
``` @@ -91,7 +91,7 @@ Kontextusérzékeny escapelés Mit jelent pontosan a kontextus szó? Ez egy hely a dokumentumban, saját szabályokkal a kiírt adatok kezelésére. A dokumentum típusától (HTML, XML, CSS, JavaScript, plain text, ...) függ, és eltérhet annak konkrét részeiben. Például egy HTML dokumentumban számos ilyen hely (kontextus) van, ahol nagyon eltérő szabályok érvényesek. Talán meglepődik, mennyi van belőlük. Íme az első négy: -```html +```latte
#szöveg
@@ -108,7 +108,7 @@ Talán meglepő, de speciális szabályok érvényesek a `