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..f12fa5faf1 --- /dev/null +++ b/ai/en/claude-code.texy @@ -0,0 +1,251 @@ +Claude Code Plugin +****************** + +
+ +The Nette plugin gives Claude deep knowledge of the framework. Instead of generic PHP advice, you get recommendations that follow Nette conventions – from presenters and forms to Latte templates and database queries. + +The plugin includes: +- **10 skills** covering all major areas of Nette development +- **Automatic validation** that catches errors in Latte and NEON files +- **MCP Inspector integration** for real-time application introspection + +
+ + +Installation +============ + +If you haven't installed Claude Code yet, see the [complete setup guide |getting-started]. Once Claude Code is running, install the Nette plugin: + +```shell +/plugin marketplace add nette/claude-code +/plugin install nette@nette +``` + + +How Skills Work +=============== + +You don't need to activate skills manually. They turn on automatically based on what you're talking about. + +- Ask about "presenter structure" → the `nette-architecture` skill activates +- Ask about "form validation" → the `nette-forms` skill activates +- Ask about "Latte filters" → the `latte-templates` skill activates + +This means you get relevant, context-aware help without having to think about which skill you need. + + +Available Skills +================ + +Here's what each skill covers: + + +nette-architecture +------------------ + +When you're designing your application structure, this skill guides you through: + +- **Directory organization** – Where to put presenters, services, entities, and components +- **Module design** – How to split your application into logical modules (Admin, Front, Api) +- **Presenter patterns** – When to use base presenters, how to handle authentication +- **Evolution strategy** – Start minimal, grow organically, refactor when needed + +The key principle: Don't over-engineer. Create subdirectories when you have 5+ related files, not before. + + +nette-configuration +------------------- + +Everything about the DI container and NEON configuration: + +- **Service registration** – How to define services in `services.neon` +- **Autowiring** – When it works automatically and when you need explicit configuration +- **Parameters** – How to use configuration parameters across your application +- **Extensions** – Working with DI extensions from Nette and third parties + + +nette-database +-------------- + +Covers both the raw SQL approach and the Database Explorer: + +- **Database Explorer** – Using `Selection` for queries, `ActiveRow` for entities +- **Entity conventions** – The `Row` suffix pattern, type hints with `@property-read` +- **Relationships** – Navigating foreign keys with colon notation +- **When to use what** – Explorer for CRUD, raw SQL for complex analytics + +Example: Claude knows that `->where('category.slug', $slug)` automatically joins the category table. + + +nette-forms +----------- + +Creating and handling forms the Nette way: + +- **Controls** – All built-in controls from text inputs to file uploads +- **Validation** – Built-in rules, custom validators, conditional validation +- **Rendering** – Manual rendering, Bootstrap integration, custom renderers +- **Patterns** – Create/edit forms, form components, AJAX submissions + + +nette-schema +------------ + +Data validation and normalization with the Schema component: + +- **Expect class** – Building validation schemas for arrays and objects +- **Configuration schemas** – Validating NEON configuration in DI extensions +- **Type coercion** – Automatic conversion of strings to integers, dates, etc. +- **Custom validators** – Adding your own validation rules + +Example: Claude knows how to create schemas like: + +```php +$schema = Expect::structure([ + 'name' => Expect::string()->required(), + 'age' => Expect::int()->min(0)->max(120), + 'email' => Expect::string(), + 'roles' => Expect::listOf('string')->default([]), +]); +``` + + +nette-testing +------------- + +Writing tests with Nette Tester: + +- **Test structure** – The `.phpt` format, `@testCase` annotation, file organization +- **Assertions** – All `Assert::*` methods: `same()`, `equal()`, `exception()`, `match()`, and more +- **Fixtures** – Setting up test data, mocking dependencies, database transactions +- **Running tests** – Command-line options, parallel execution, code coverage + +Example: Claude can generate proper test files: + +```php +/** @testCase */ +class UserServiceTest extends TestCase +{ + public function testCreateUser(): void + { + $service = new UserService($this->mockDatabase()); + $user = $service->create(['name' => 'John']); + Assert::same('John', $user->name); + } +} +``` + + +nette-utils +----------- + +The utility classes that make PHP development easier: + +- **Arrays** – `Nette\Utils\Arrays` with `get()`, `getRef()`, `map()`, `flatten()`, and more +- **Strings** – Unicode-safe operations: `webalize()`, `truncate()`, `contains()`, `startsWith()` +- **Finder** – File system traversal with filtering by name, size, date +- **Image** – Resize, crop, sharpen with automatic format detection +- **Json** – Safe JSON encoding/decoding with proper error handling +- **Validators** – Email, URL, numeric validation helpers +- **DateTime** – Immutable date/time with Czech locale support + + +frontend-development +-------------------- + +Integrating frontend tools with Nette: + +- **Vite** – Setting up Vite for modern JavaScript/TypeScript development, HMR configuration +- **Nette Assets** – Using the asset system for cache-busted URLs in production +- **Tailwind CSS** – Configuration for Tailwind with Latte templates, purging unused styles +- **ESLint & Prettier** – Code quality tools integration +- **Build scripts** – npm/package.json scripts for development and production builds + +Claude can help configure `vite.config.js` to work with Nette's directory structure and generate proper asset references in Latte templates. + + +latte-templates +--------------- + +Everything about the Latte templating engine: + +- **Syntax** – Tags, filters, blocks, and inheritance +- **Security** – Auto-escaping, content-aware output +- **Custom filters** – Creating and registering your own filters +- **Template classes** – Using typed templates for better IDE support + + +neon-format +----------- + +The NEON configuration format: + +- **Syntax** – Mappings, sequences, entities, and multiline strings +- **Common patterns** – Service definitions, parameter references +- **Debugging** – Finding and fixing syntax errors + + +Automatic Validation +==================== + +One of the most useful features is automatic validation. After every file edit, the plugin checks for errors: + +| What | How It Works | +|------|--------------| +| **Latte templates** | Runs `latte-lint` to check syntax after every `.latte` edit | +| **NEON files** | Validates NEON syntax after every `.neon` edit | +| **Tracy errors** | Watches the exception log and alerts Claude about new errors | + +If there's a syntax error in your Latte template, Claude knows about it immediately and can suggest a fix. No more discovering errors in the browser. + + +Plugin for Framework Contributors +================================= + +If you're contributing to Nette itself, there's an additional plugin with coding standards: + +```shell +/plugin install nette-dev@nette +``` + +| Skill | What It Covers | +|-------|----------------| +| `php-coding-standards` | Nette's PHP coding style – indentation, naming, structure | +| `php-doc` | PHPDoc conventions – when to document, what format to use | +| `commit-messages` | How to write commit messages for Nette repositories | + + +Automatic PHP Style Fixing +========================== + +For automatic code style fixing after every PHP file edit, install the optional php-fixer plugin: + +```shell +/plugin install php-fixer@nette +/install-php-fixer +``` + +The second command installs `nette/coding-standard` globally. After that, every PHP file you edit will be automatically formatted according to Nette coding standards. + + +MCP Inspector Integration +========================= + +The plugin works even better with [MCP Inspector |mcp-inspector] – a tool that lets Claude see your actual application state. With MCP Inspector, Claude can: + +- Query your real database schema instead of guessing +- List your registered DI services and their configuration +- Read Tracy error logs for debugging +- Match URLs to presenters using your actual routes + +Install it with a single command: + +```shell +/install-mcp-inspector +``` + +Then restart Claude Code. See the [MCP Inspector documentation |mcp-inspector] for all 20 available tools. + +{{composer: nette/claude-code}} diff --git a/ai/en/getting-started.texy b/ai/en/getting-started.texy new file mode 100644 index 0000000000..fa20b5da1f --- /dev/null +++ b/ai/en/getting-started.texy @@ -0,0 +1,260 @@ +Getting Started +*************** + +
+ +Ready to try [vibe coding |guide] with Nette? This guide walks you through the complete setup: + +- Choosing and installing an AI tool +- Setting up MCP Inspector so AI can see your application +- Making your first AI-assisted changes + +The whole process takes about 10 minutes. Let's get started! + +
+ + +Choosing Your AI Tool +===================== + +Nette AI tools work with any MCP-compatible AI assistant. We recommend **Claude Code** for the best experience – it has a dedicated Nette plugin with deep framework knowledge and automatic code validation. + +Other options include **Cursor**, **VS Code with Continue**, and other MCP-compatible tools. See [Other AI Tools |#other-ai-tools] at the end of this guide. + + +What You'll Need +================ + +Before we begin, make sure you have: + +- **A Nette project** – existing or new (`composer create-project nette/web-project`) +- **PHP 8.2+** – required for MCP Inspector +- **An AI tool** – we'll install Claude Code below (Claude Pro costs $20/month) + + +Installation on macOS and Linux +=============================== + +```shell +curl -fsSL https://claude.ai/install.sh | bash +``` + +On macOS, you can alternatively use Homebrew: + +```shell +brew install --cask claude-code +``` + +After installation, skip to [Starting Claude Code |#starting-claude-code]. + + +Installation on Windows via WSL +=============================== + +Claude Code requires a Unix environment. On Windows, use WSL: + +```shell +wsl --install +``` + +This installs Ubuntu. **Restart your computer** after installation. + +After restart, launch Ubuntu: + +```shell +ubuntu +``` + +First launch will ask for a username and password – you'll need it for `sudo` later. + +Installing Claude Code in WSL in the Ubuntu terminal: + +```shell +curl -fsSL https://claude.ai/install.sh | bash +``` + +Your Windows drives are mounted under `/mnt/`: +- `C:\Users\Jan\Projects` → `/mnt/c/Users/Jan/Projects` +- `D:\Work` → `/mnt/d/Work` + +From Windows, access Linux files via `\\wsl$\Ubuntu\home\username` in Explorer. + + +Starting Claude Code +==================== + +Great, you have Claude Code installed! Let's start it up. + +Navigate to your project directory: + +```shell +# Windows (WSL) +cd /mnt/c/Users/Jan/Projects/my-app + +# macOS/Linux +cd ~/projects/my-app +``` + +Start Claude Code: + +```shell +claude +``` + +The first time you run it, Claude Code will ask you to authenticate. It will open a browser window where you can log in to your Anthropic account. After successful authentication, you'll see the `claude>` prompt and you're ready to go. + +You can also use Claude Code "on the web":https://claude.ai/code or via the "desktop app":https://claude.com/download, but local installation provides the best experience with direct file access. + + +Adding the Nette Plugin +======================= + +Now let's give Claude deep knowledge of Nette. First, add the Nette marketplace and enable auto-updating: + +```shell +/plugin marketplace add nette/claude-code +``` + +Then install the plugin: + +```shell +/plugin install nette@nette +``` + +That's it! The plugin is now active. It includes 10 specialized skills that automatically activate based on what you're working on. When you ask about forms, it knows about Nette Forms. When you ask about templates, it knows about Latte. + + +Setting Up MCP Inspector +======================== + +The final piece is MCP Inspector, which lets Claude see your actual application – your services, database schema, routes, and error logs. + +The easiest way to install it is through Claude Code: + +```shell +/install-mcp-inspector +``` + +This command adds the `nette/mcp-inspector` package to your project and configures everything automatically. + +Alternatively, you can install it manually with Composer: + +```shell +composer require nette/mcp-inspector +``` + +**Important:** After installing MCP Inspector, restart Claude Code (type `/exit` and run `claude` again) to activate the connection. + + +Testing Your Setup +================== + +Let's verify everything works. Try these prompts: + + +Test the Plugin Knowledge +------------------------- + +Type: + +``` +What's the recommended directory structure for a Nette application? +``` + +Claude should respond with detailed information about presenters, models, templates, and configuration – knowledge that comes from the `nette-architecture` skill. + + +Test MCP Inspector +------------------ + +Type: + +``` +What services do I have registered in my DI container? +``` + +If MCP Inspector is working, Claude will call `di_get_services()` and show you the actual services from your application. If you see a list of your real services, congratulations – everything is set up correctly! + + +Test Database Introspection +--------------------------- + +If your application uses a database, try: + +``` +What tables do I have? Show me the columns in the user table. +``` + + +Your First Real Task +==================== + +Now that everything is set up, let's do something useful. Try this prompt: + +``` +I need a simple ArticlePresenter with list and detail actions. +Generate the presenter, templates, and tell me what routes I need. +``` + +Watch as Claude generates a complete, working presenter following Nette conventions. It will: +- Create the presenter class with proper type hints +- Generate Latte templates for both actions +- Suggest the appropriate route configuration + +If you have MCP Inspector set up and an `article` table in your database, try: + +``` +Look at my article table and generate an ArticleRow entity with proper type hints. +``` + + +Other AI Tools +============== + +While we recommend Claude Code for the best Nette experience, MCP Inspector works with any MCP-compatible tool. + + +Cursor +------ + +Cursor is a popular AI-first code editor. To use MCP Inspector with Cursor: + +1. Install MCP Inspector: `composer require nette/mcp-inspector` +2. Create `.cursor/mcp.json` in your project: + +```json +{ + "mcpServers": { + "nette-inspector": { + "command": "php", + "args": ["vendor/bin/mcp-inspector"] + } + } +} +``` + +3. Restart Cursor + +Note: Cursor doesn't have the Nette-specific skills that the Claude Code plugin provides, but MCP Inspector will still give it access to your application's services, database, routes, and logs. + + +VS Code + Continue +------------------ + +Continue is an open-source AI coding assistant for VS Code. Configure MCP Inspector in Continue's settings following their MCP documentation. + + +Other MCP Tools +--------------- + +Any tool supporting the Model Context Protocol can use MCP Inspector. See the [MCP Inspector manual configuration |mcp-inspector#manual-mcp-configuration] for setup instructions. + + +What's Next +=========== + +You're now ready for AI-assisted Nette development! Here's where to go from here: + +- [MCP Inspector |mcp-inspector] – Learn about all 20 introspection tools +- [Claude Code Plugin |claude-code] – Explore all 13 skills (Claude Code users) +- [Tips & Best Practices |tips] – Get the most out of your AI assistant diff --git a/ai/en/guide.texy b/ai/en/guide.texy new file mode 100644 index 0000000000..b0d98486c5 --- /dev/null +++ b/ai/en/guide.texy @@ -0,0 +1,128 @@ +Vibe Coding +*********** + +
+ +Vibe coding is a new way of programming where you describe what you want in plain language and AI writes the code for you. Nette is ideal for this style of development – strict dependency injection, strong typing, and clear conventions allow AI to generate precise, working code. + +- **MCP Inspector** – Gives any AI tool real-time access to your application +- **Claude Code Plugin** – Deep Nette knowledge for Claude Code users +- **Best Practices** – Proven patterns for effective AI collaboration + +
+ + +What is Vibe Coding? +==================== + +"The hottest new programming language is English." + +That's the core idea behind vibe coding – instead of writing every line yourself, you describe your intent and let AI handle the implementation. Want a presenter for managing products? Just say so. Need a form with validation? Describe the fields and rules. + +But here's the important part: **AI doesn't replace programmers**. It's a powerful assistant that accelerates routine work: + +- Generate boilerplate code (presenters, forms, entities) in seconds +- Understand existing code and explain how it works +- Find bugs and suggest fixes +- Write tests based on your implementation + +The catch? AI doesn't truly know your application. It sees only what you show it and guesses the rest based on patterns it learned during training. That's where Nette AI tools come in. + + +Why Nette is Perfect for AI +=========================== + +Not all frameworks work equally well with AI. Nette has properties that make it exceptionally suited for AI-assisted development: + +**Strict Dependency Injection** + +In Nette, all services are registered in the DI container. AI can inspect exactly what services exist and how they're configured – no guessing required. + +**Strong Typing** + +Type hints on methods and properties mean AI generates code that actually works. Fewer runtime errors, less debugging. + +**Clear Conventions** + +Presenters, components, templates – everything has its place. AI can follow these patterns and produce code that looks like it was written by an experienced Nette developer. + +The key principle: + +.**"Without MCP, AI guesses. With MCP, AI knows."** + + +How It Works +============ + +The magic happens through **MCP (Model Context Protocol)** – an open standard for connecting AI assistants to external data sources. Instead of guessing based on training data, AI can query your actual application state. + +Here's the flow: + +1. **You** describe what you want: "Create an entity for the product table" +2. **AI tool** (Claude, Cursor, etc.) needs to know your database schema +3. **MCP Inspector** queries your application and returns the actual schema +4. **AI** generates code that matches your real database + +No hallucinations. No guessing. Just accurate code. + + +Nette AI Tools +============== + + +MCP Inspector +------------- + +The core of Nette's AI integration. MCP Inspector is an MCP server that gives **any compatible AI tool** real-time access to your application: + +| What AI Can See | Examples | +|-----------------|----------| +| **DI Container** | Services, parameters, extensions | +| **Database** | Tables, columns, relationships | +| **Router** | Routes, URL matching, generation | +| **Tracy** | Exceptions, warnings, logs | + +MCP Inspector works with Claude Code, Cursor, VS Code with Continue, and any other tool that supports the MCP protocol. + +[Learn more about MCP Inspector |mcp-inspector] + + +Claude Code Plugin +------------------ + +For users of Claude Code, there's an additional plugin that gives Claude deep knowledge of Nette conventions. It includes 10 specialized "skills" that activate automatically: + +| Skill | What It Covers | +|-------|----------------| +| nette-architecture | Presenters, modules, directory structure | +| nette-database | Database Explorer, entities, queries | +| nette-forms | Controls, validation, rendering | +| latte-templates | Syntax, filters, security | +| + 6 more... | [See complete list |claude-code] | + +The plugin also automatically validates Latte templates and NEON files after every edit. + +[Learn more about Claude Code Plugin |claude-code] + + +Other AI Tools +-------------- + +MCP Inspector works with any MCP-compatible tool. Setup guides for additional tools are coming soon: + +- **Cursor** – Popular AI-first code editor +- **VS Code + Continue** – Open-source AI coding assistant +- **Gemini CLI** – Google's command-line AI tool + + +Getting Started +=============== + +Ready to try vibe coding with Nette? The setup takes about 10 minutes: + +1. **Choose your AI tool** – We recommend Claude Code for the best Nette experience +2. **Install MCP Inspector** – The core that gives AI access to your application +3. **Start coding** – Describe what you want and let AI help + +[Complete setup guide |getting-started] + diff --git a/ai/en/mcp-inspector.texy b/ai/en/mcp-inspector.texy new file mode 100644 index 0000000000..5965a94677 --- /dev/null +++ b/ai/en/mcp-inspector.texy @@ -0,0 +1,448 @@ +MCP Inspector +************* + +
+ +MCP Inspector is the bridge between **any AI tool** and your Nette application. It allows AI assistants to look directly at your running app – to see what services you have registered, what your database schema looks like, which routes are defined, and what errors have occurred. + +This is what makes the difference between AI that guesses and AI that knows. + +
+ + +Supported AI Tools +================== + +MCP Inspector works with any tool that supports the **Model Context Protocol (MCP)**: + +- **[Claude Code |claude-code]** – Full support with dedicated Nette plugin +- **Cursor** – Configure via `.cursor/mcp.json` +- **VS Code + Continue** – Configure via Continue settings +- **Any MCP-compatible tool** – See [manual configuration |#manual-mcp-configuration] + + +Why MCP Matters +=============== + +Imagine you ask your AI: "Generate an entity for the product table." + +Without MCP Inspector, the AI has to guess what columns your table has. It might assume common patterns like `id`, `name`, `price` – but what if your table has different columns? What if `price` is called `unit_price`? What if you have a `currency_id` foreign key? + +With MCP Inspector, the AI doesn't guess. It calls `db_get_columns("product")` and sees your actual schema: + +The result is code that actually works with your database, not code you have to fix. + + +Installation +============ + +If you're using the [Nette plugin for Claude Code |claude-code], installation is simple: + +```shell +/install-mcp-inspector +``` + +This command adds `nette/mcp-inspector` to your project and configures everything automatically. + +For other AI tools or manual installation: + +```shell +composer require nette/mcp-inspector +``` + +Then configure your AI tool to use the MCP server – see [manual configuration |#manual-mcp-configuration] below. + +**Important:** After installation, restart your AI tool. The MCP server only connects when the tool starts. + + +How It Works +============ + +MCP Inspector runs as a background process that your AI tool can communicate with. When AI needs information about your application, it sends a request to MCP Inspector, which: + +1. Loads your application's DI container (using `App\Bootstrap`) +2. Executes the requested query (get services, read database schema, etc.) +3. Returns the result to the AI + +All operations are **read-only**. MCP Inspector can't modify your database, change configuration, or execute commands. + + +DI Container Tools +================== + +These tools let AI explore your service definitions. + + +di_get_services +--------------- + +Lists all registered services. You can filter by name or type. + +When AI asks "What mail services do I have?", it calls: + +``` +di_get_services("mail") +``` + +And gets a list like: + +``` +- mail.mailer (Nette\Mail\Mailer) +- App\Model\QueueMailer +- App\Core\SmtpTransport +``` + + +di_get_service +-------------- + +Gets detailed information about a specific service – how it's created, what setup methods are called, what tags it has. + + +di_get_parameters +----------------- + +Reads configuration parameters. Want to know what your database settings are? + +``` +di_get_parameters("database") +``` + +Note: Sensitive values (passwords, tokens, API keys) are automatically masked. + + +di_find_by_tag +-------------- + +Finds services with a specific tag. Useful for discovering CLI commands: + +``` +di_find_by_tag("console.command") +``` + + +di_find_by_type +--------------- + +Finds services implementing a specific interface: + +``` +di_find_by_type("Nette\\Security\\Authenticator") +``` + + +di_get_extensions +----------------- + +Lists all registered DI extensions with their configuration. + + +Database Tools +============== + +These tools give AI visibility into your database structure. + + +db_get_tables +------------- + +Lists all tables in your database. + + +db_get_columns +-------------- + +Gets detailed column information for a table – types, whether they're nullable, default values, and foreign key relationships. + +``` +db_get_columns("order") +``` + +Returns something like: + +``` +- id: int (PRIMARY KEY) +- customer_id: int (FK → customer.id) +- status: varchar(20) +- total: decimal(10,2) +- created_at: datetime +``` + + +db_get_relationships +-------------------- + +Shows all foreign key relationships in your database – which tables reference which other tables. + + +db_get_indexes +-------------- + +Lists indexes for a specific table. + + +db_explain_query +---------------- + +Runs `EXPLAIN` on a SELECT query to analyze its performance. AI can use this to suggest query optimizations. + + +db_generate_entity +------------------ + +The most useful tool for quick development. Given a table name, it generates a complete PHP entity class with proper type hints: + +``` +db_generate_entity("product") +``` + +Generates: + +```php +/** + * @property-read int $id + * @property-read string $name + * @property-read float $unit_price + * @property-read ?CategoryRow $category + * @property-read DateTimeImmutable $created_at + */ +final class ProductRow extends Table\ActiveRow +{ +} +``` + + +Router Tools +============ + +These tools help AI understand your URL structure. + + +router_get_routes +----------------- + +Lists all registered routes with their masks and default values. + + +router_match_url +---------------- + +Given a URL, finds which presenter and action handles it: + +``` +router_match_url("/admin/products/edit/5") +``` + +Returns: + +``` +Presenter: Admin:Product +Action: edit +Parameters: id=5 +``` + + +router_generate_url +------------------- + +Generates a URL for a given presenter and action: + +``` +router_generate_url("Admin:Product:edit", {"id": 5}) +``` + + +Tracy Tools +=========== + +These tools let AI see error logs and help with debugging. They're incredibly useful when something goes wrong – instead of you describing the error, AI can read it directly. + + +tracy_get_last_exception +------------------------ + +Gets the most recent exception from Tracy's log, including the full stack trace. When something breaks, this is the first thing AI checks. + +``` +tracy_get_last_exception() +``` + +Returns the exception class, message, file, line number, and complete stack trace. AI can analyze this to identify the root cause and suggest a fix. + +Example response: +``` +Exception: Nette\Database\UniqueConstraintViolationException +Message: Duplicate entry 'john@example.com' for key 'email' +File: /app/Model/UserService.php:45 +Stack trace: + #0 /app/Presentation/Admin/UserPresenter.php:32 + #1 /vendor/nette/application/src/... +``` + + +tracy_get_exceptions +-------------------- + +Lists recent exception files from Tracy's log directory. Useful for finding patterns or recurring issues. + +``` +tracy_get_exceptions(5) +``` + +Returns the 5 most recent exception files with timestamps. You can then use `tracy_get_exception_detail` to examine any of them. + + +tracy_get_exception_detail +-------------------------- + +Gets the complete details of a specific exception file. Use this when you want to examine an older exception, not just the latest one. + +``` +tracy_get_exception_detail("exception-2024-01-15-143022-abc123.html") +``` + + +tracy_get_warnings +------------------ + +Shows recent PHP warnings and notices from Tracy's log. These often indicate problems that don't crash the application but should be fixed. + +``` +tracy_get_warnings(10) +``` + +Common warnings AI can help fix: +- Undefined array key +- Deprecated function calls +- Type mismatch warnings + + +tracy_get_log +------------- + +Reads entries from any Tracy log level. Tracy supports multiple log files: `error.log`, `warning.log`, `info.log`, and custom levels. + +``` +tracy_get_log("error", 20) +``` + +This reads the last 20 entries from the error log. Useful for seeing a history of issues, not just the most recent one. + + +Creating Custom Tools +===================== + +You can extend MCP Inspector with your own tools. This is useful if you have application-specific data that AI should be able to query. + +Create a class implementing the `Toolkit` interface: + +```php +use Mcp\Capability\Attribute\McpTool; +use Nette\McpInspector\Toolkit; +use Nette\McpInspector\Bridge\BootstrapBridge; + +class OrderToolkit implements Toolkit +{ + public function __construct( + private BootstrapBridge $bridge, + ) {} + + /** + * Get pending orders count and total value. + */ + #[McpTool(name: 'orders_get_pending_summary')] + public function getPendingSummary(): array + { + $db = $this->bridge->getContainer() + ->getByType(Nette\Database\Explorer::class); + + $result = $db->table('order') + ->where('status', 'pending') + ->select('COUNT(*) AS count, SUM(total) AS total') + ->fetch(); + + return [ + 'count' => $result->count, + 'total' => $result->total, + ]; + } +} +``` + +Register it in `mcp-config.neon`: + +```neon +toolkits: + - App\Mcp\OrderToolkit +``` + +Now AI can call `orders_get_pending_summary()` to get real-time order statistics. + + +Configuration +============= + +MCP Inspector works out of the box with the default Nette project structure. If your setup is different, create `mcp-config.neon` in your project root: + +```neon +# Path to Bootstrap file (if not in default location) +bootstrap: src/Bootstrap.php + +# Bootstrap class name (if different from default) +bootstrapClass: MyApp\Bootstrap + +# Additional custom toolkits +toolkits: + - App\Mcp\OrderToolkit + - App\Mcp\CustomerToolkit +``` + + +Manual MCP Configuration +------------------------ + +For AI tools other than Claude Code (which configures automatically via the plugin), add MCP Inspector to your tool's configuration: + +**For most MCP-compatible tools**, create `.mcp.json` in your project root: + +```json +{ + "mcpServers": { + "nette-inspector": { + "command": "php", + "args": ["vendor/bin/mcp-inspector"] + } + } +} +``` + +**For Cursor**, add to `.cursor/mcp.json`: + +```json +{ + "mcpServers": { + "nette-inspector": { + "command": "php", + "args": ["vendor/bin/mcp-inspector"] + } + } +} +``` + +Consult your AI tool's documentation for the exact configuration location. + + +Security Considerations +======================= + +MCP Inspector is designed for development environments. Here's what you should know: + +**Read-only by design** – All tools only read data, never modify it. + +**Database protection** – The `db_explain_query` tool only accepts SELECT, SHOW, DESCRIBE, and EXPLAIN queries. INSERT, UPDATE, DELETE, and other modifying queries are rejected. + +**Sensitive data masking** – Configuration values containing words like "password", "secret", "token", or "apikey" are automatically masked with `***MASKED***`. + +**Do not expose in production** – MCP Inspector should only run on development machines. It provides detailed information about your application internals that you don't want exposed publicly. + +{{composer: nette/mcp-inspector}} diff --git a/ai/en/tips.texy b/ai/en/tips.texy new file mode 100644 index 0000000000..586f558519 --- /dev/null +++ b/ai/en/tips.texy @@ -0,0 +1,296 @@ +Tips for AI-Assisted Development +******************************** + +
+ +AI is a powerful tool, but like any tool, you get better results when you know how to use it well. This page collects practical advice from real-world experience with AI-assisted Nette development. + +- How to write prompts that get better results +- Making the most of MCP Inspector +- Proven workflows for common tasks +- Mistakes to avoid + +
+ + +Writing Better Prompts +====================== + + +The Art of Being Specific +------------------------- + +The single biggest improvement you can make is being specific. Compare these two prompts: + +**Vague prompt:** + +``` +Create a form +``` + +This gives the AI almost no context. What form? What fields? What validation? The AI has to make assumptions, and those assumptions might not match what you need. + +**Specific prompt:** + +``` +Create a ProductForm with: +- name: text field, required, max 100 characters +- price: float field, required, must be positive +- description: textarea, optional +- category: select from CategoryRow entities + +Use Bootstrap 5 rendering. The form should work for both creating new products and editing existing ones. +``` + +Now the AI knows exactly what you need and can generate code that works on the first try. + + +Point to Existing Patterns +-------------------------- + +Your codebase already has patterns. Instead of explaining them, point the AI to examples: + +``` +Create an OrderPresenter. Follow the same patterns as ProductPresenter – +same structure, same way of handling forms, same template organization. +``` + +The AI will read ProductPresenter and replicate the patterns you're already using. + + +Let MCP Do the Heavy Lifting +---------------------------- + +If you have MCP Inspector installed (and you should!), don't explain your application – let the AI discover it: + +**Instead of:** + +``` +My product table has columns: id (int), name (varchar), price (decimal), +category_id (int, foreign key to category), created_at (datetime)... +``` + +**Just say:** + +``` +Generate an entity for the product table. +``` + +The AI will call `db_get_columns("product")` and see the actual schema. The generated entity will match your real database, including any columns you might have forgotten to mention. + + +Give Context About Your Goals +----------------------------- + +AI can't read your mind. If there's a reason behind your request, share it: + +``` +I need to optimize the product listing page. It's currently loading +all products at once, which is slow when there are thousands of items. +The page needs to support filtering by category and sorting by price or name. +``` + +This helps the AI suggest an appropriate solution (pagination, lazy loading, caching) rather than just blindly implementing what you asked for. + + +Working with MCP Inspector +========================== + +MCP Inspector is most powerful when you use it strategically. + + +Explore Before You Build +------------------------ + +Starting a new feature? Let the AI understand the context first: + +``` +I'm going to add order tracking. Before we start: +1. What services do I have related to orders? +2. What does my order table look like? +3. What routes handle order-related pages? +``` + +This gives the AI context about your existing code, so the new feature fits naturally. + + +Debug Smarter +------------- + +When something goes wrong, don't describe the error – let the AI see it: + +``` +Something broke. Check the Tracy log for the last exception +and tell me what went wrong. +``` + +The AI will call `tracy_get_last_exception()`, read the stack trace, and can often identify the problem faster than you could explain it. + + +Verify Before You Create +------------------------ + +Before adding new routes or links, verify what exists: + +``` +What presenter handles /admin/products/edit? I want to make sure +I'm not creating something that conflicts. +``` + + +Common Workflows +================ + +Here are proven approaches for common tasks. + + +Creating a Complete CRUD +------------------------ + +Don't ask for one piece at a time. Give the AI the full picture: + +``` +Create a complete CRUD for managing products: + +1. ProductPresenter with actions: list, add, edit, delete +2. ProductForm as a component (works for both add and edit) +3. Latte templates for all actions +4. Route suggestions + +Use the actual product table schema. Follow the patterns +in CategoryPresenter if it exists. +``` + + +Adding a New Feature +-------------------- + +Break it down into phases that build on each other: + +``` +I need to add customer reviews to products. + +Phase 1 - Data layer: +- Look at the product table +- Suggest the review table schema +- Create ReviewRow entity + +Phase 2 - Business logic: +- Create ReviewService for CRUD operations +- Add methods to get reviews for a product + +Phase 3 - UI: +- Add review display to ProductPresenter:detail +- Create ReviewForm for submitting reviews +``` + + +Refactoring Existing Code +------------------------- + +Let the AI understand before it changes: + +``` +Analyze the OrderService class. What does each method do? +Are there any code smells or improvements you'd suggest? +``` + +Then: + +``` +The calculateTotal method is doing too much. Split it into +smaller methods while keeping the same public interface. +``` + + +Mistakes to Avoid +================= + + +Not Reviewing Generated Code +---------------------------- + +AI generates code quickly, but that doesn't mean every line is perfect. Always review: + +- **Database queries** – Are they efficient? Do they need indexes? +- **Security** – Is input validated? Are there authorization checks? +- **Edge cases** – What happens with empty data? Null values? + +AI is very good, but it's still an assistant. You're the developer responsible for the final code. + + +Ignoring Validation Feedback +---------------------------- + +If you're using the [Claude Code plugin |claude-code], it validates your Latte templates and NEON files automatically. When it reports an error, the AI knows about it. Instead of manually fixing the error, just say: + +``` +Fix the error you just created. +``` + +The AI will read the validation output and correct the mistake. + + +Forgetting Service Registration +------------------------------- + +When the AI creates a new service class, it sometimes forgets to register it in the DI container. If you get "Service not found" errors, ask: + +``` +What changes do I need in services.neon for the new OrderExportService? +``` + + +Asking for Too Much at Once +--------------------------- + +While AI can handle complex tasks, sometimes it helps to break them down: + +**Too ambitious:** + +``` +Build me a complete e-commerce system with product catalog, +shopping cart, checkout, payments, order tracking, and admin panel. +``` + +**Better approach:** + +``` +Let's build an e-commerce system step by step. Start with the product +catalog – I need to list, view, and admin products. +``` + + +When AI Excels (and When It Doesn't) +==================================== + + +AI is Great For +--------------- + +- **Boilerplate code** – Presenters, forms, entities, basic templates +- **Following patterns** – "Do it like X but for Y" +- **Understanding code** – "What does this method do?" +- **Generating tests** – Given implementation, create tests +- **Refactoring** – Improving code structure while keeping behavior + + +Consider Manual Coding For +-------------------------- + +- **Complex business logic** – Domain rules that require careful thinking +- **Performance-critical code** – Algorithms that need optimization +- **Security-sensitive code** – Authentication, authorization, encryption +- **Novel solutions** – Things that don't follow existing patterns + +AI is a multiplier, not a replacement. It makes good developers faster, but it still needs a good developer guiding it. + + +Final Thoughts +============== + +The best way to learn AI-assisted development is to practice. Start with simple tasks, pay attention to what works, and gradually take on more complex projects. + +And remember: the AI is your assistant. You're still the developer. You make the decisions, you review the code, and you're responsible for the quality of the final product. + +Happy coding! diff --git a/ai/meta.json b/ai/meta.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/ai/meta.json @@ -0,0 +1 @@ +{} diff --git a/application/cs/creating-links.texy b/application/cs/creating-links.texy index 011beea750..8658cb3646 100644 --- a/application/cs/creating-links.texy +++ b/application/cs/creating-links.texy @@ -12,7 +12,7 @@ Tvořit odkazy v Nette je jednoduché, jako ukazovat prstem. Stačí jen namíř -Díky [obousměrnému routování |routing] nebudete nikdy muset do šablon či kódu zapisovat navrdo URL adresy vaší aplikace, které se mohou později měnit, nebo je komplikovaně skládat. V odkazu stačí uvést presenter a akci, předat případné parametry a framework už URL vygeneruje sám. Vlastně je to velice podobné, jako když voláte funkci. To se vám bude líbit. +Díky [obousměrnému routování |routing] nebudete nikdy muset do šablon či kódu zapisovat natvrdo URL adresy vaší aplikace, které se mohou později měnit, nebo je komplikovaně skládat. V odkazu stačí uvést presenter a akci, předat případné parametry a framework už URL vygeneruje sám. Vlastně je to velice podobné, jako když voláte funkci. To se vám bude líbit. V šabloně presenteru diff --git a/application/cs/presenters.texy b/application/cs/presenters.texy index 04cd942e32..f00a645bba 100644 --- a/application/cs/presenters.texy +++ b/application/cs/presenters.texy @@ -393,6 +393,8 @@ public function actionShow(int $id, ?string $slug = null): void } ``` +Kompletní vzor, který kombinuje routovací filtry s `canonicalize()` pro generování SEO-friendly URL, najdete v návodu [Hezké URL se slugem |best-practices:pretty-urls]. + Události -------- diff --git a/application/cs/routing.texy b/application/cs/routing.texy index 3a2e753731..b5a7648612 100644 --- a/application/cs/routing.texy +++ b/application/cs/routing.texy @@ -325,6 +325,8 @@ Obecné filtry dávají možnost upravit chování routy naprosto jakýmkoliv zp Pokud má parametr definovaný vlastní filtr a současně existuje obecný filtr, provede se vlastní `FilterIn` před obecným a naopak obecný `FilterOut` před vlastním. Tedy uvnitř obecného filtru jsou hodnoty parametrů `presenter` resp. `action` zapsané ve stylu PascalCase resp. camelCase. +Praktické využití těchto filtrů — generování SEO-friendly URL typu `/clanek/123-jak-upect-chleba` bez zásahu do šablon — najdete v návodu [Hezké URL se slugem |best-practices:pretty-urls]. + Jednosměrky OneWay ------------------ diff --git a/application/cs/templates.texy b/application/cs/templates.texy index 568b40837e..5e4452a586 100644 --- a/application/cs/templates.texy +++ b/application/cs/templates.texy @@ -274,7 +274,7 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template #[Latte\Attributes\TemplateFunction] public function isWeekend(DateTimeInterface $date): bool { - return $date->format('N') >= 6 + return $date->format('N') >= 6; } } ``` diff --git a/application/en/presenters.texy b/application/en/presenters.texy index 7658686bbf..272c3216f9 100644 --- a/application/en/presenters.texy +++ b/application/en/presenters.texy @@ -393,6 +393,8 @@ public function actionShow(int $id, ?string $slug = null): void } ``` +For a complete pattern that combines route filters with `canonicalize()` to produce SEO-friendly URLs, see [Pretty URLs with Slugs |best-practices:pretty-urls]. + Events ------ diff --git a/application/en/routing.texy b/application/en/routing.texy index 4b83f317f2..8cf31e3c99 100644 --- a/application/en/routing.texy +++ b/application/en/routing.texy @@ -325,6 +325,8 @@ General filters provide the ability to modify the route's behavior in absolutely If a parameter has its own filter defined and a general filter also exists, the custom `FilterIn` is executed before the general one, and conversely, the general `FilterOut` is executed before the custom one. Thus, inside the general filter, the values of the parameters `presenter` and `action` are written in PascalCase or camelCase style, respectively. +See [Pretty URLs with Slugs |best-practices:pretty-urls] for a practical use of these filters — generating SEO-friendly URLs like `/article/123-how-to-bake-bread` without modifying any templates. + OneWay Flag ----------- diff --git a/application/en/templates.texy b/application/en/templates.texy index 6a5ba43fd3..c5de1f1484 100644 --- a/application/en/templates.texy +++ b/application/en/templates.texy @@ -274,7 +274,7 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template #[Latte\Attributes\TemplateFunction] public function isWeekend(DateTimeInterface $date): bool { - return $date->format('N') >= 6 + return $date->format('N') >= 6; } } ``` diff --git a/best-practices/cs/@home.texy b/best-practices/cs/@home.texy index 612d75048f..38118d8894 100644 --- a/best-practices/cs/@home.texy +++ b/best-practices/cs/@home.texy @@ -19,6 +19,7 @@ Nette Aplikace - [Dynamické snippety |dynamic-snippets] - [Jak používat atribut #Requires |attribute-requires] - [Jak správně používat POST odkazy |post-links] +- [Hezké URL se slugem |pretty-urls]
diff --git a/best-practices/cs/creating-editing-form.texy b/best-practices/cs/creating-editing-form.texy index 4530f060d4..d43c67faf8 100644 --- a/best-practices/cs/creating-editing-form.texy +++ b/best-practices/cs/creating-editing-form.texy @@ -130,7 +130,6 @@ Záznam si uložíme do property `$record`, abychom jej měli k dispozici v meto $this->facade->update($id, $data); // ... } -} ``` Nicméně, a to by mělo být **nejdůležitejším poznatkem celého kódu**, musíme se při tvorbě formuláře ujistit, že akce je skutečně `edit`. Protože jinak by ověření v metodě `actionEdit()` vůbec neproběhlo! diff --git a/best-practices/cs/editors-and-tools.texy b/best-practices/cs/editors-and-tools.texy index 13e3d10138..efbbd772ba 100644 --- a/best-practices/cs/editors-and-tools.texy +++ b/best-practices/cs/editors-and-tools.texy @@ -13,9 +13,9 @@ Rozhodně doporučujeme pro vývoj používat plnohodnotné IDE, jako je třeba **NetBeans IDE** má podporu pro Nette, Latte a NEON už vestavěnou. **PhpStorm**: nainstalujte si tyto pluginy v `Settings > Plugins > Marketplace` -- Nette framework helpers -- Latte -- NEON support +- [Nette |https://plugins.jetbrains.com/plugin/28342-nette] +- [Latte |https://plugins.jetbrains.com/plugin/24218-latte-support] nebo [Latte Pro |https://plugins.jetbrains.com/plugin/19661-latte-pro] +- [NEON |https://plugins.jetbrains.com/plugin/28338-neon] nebo [NEON / Nette support |https://plugins.jetbrains.com/plugin/18387-neon-nette-support] - Nette Tester **VS Code**: najděte v marketplace "Nette Latte + Neon" plugin. diff --git a/best-practices/cs/lets-create-contact-form.texy b/best-practices/cs/lets-create-contact-form.texy index e5d44812fa..23fde0f0c5 100644 --- a/best-practices/cs/lets-create-contact-form.texy +++ b/best-practices/cs/lets-create-contact-form.texy @@ -48,9 +48,6 @@ Komponentu `contactForm` necháme vykreslit v šabloně `Home/default.latte`: Pro samotné odeslání emailu vytvoříme novou třídu, kterou nazveme `ContactFacade` a umístíme ji do souboru `app/Model/ContactFacade.php`: ```php -addRoute('clanek/[-]', 'Article:detail'); +``` + +Maska `[-]` říká: po ID může (ale nemusí) následovat pomlčka a slug. Routa přijímá `/clanek/123` i `/clanek/123-cokoli`. + +Poznámka k parametru ``: defaultně matchuje libovolné znaky **kromě lomítka** — přesně to, co chceme. Pokud napíšete ``, parametr bude matchovat i lomítka, takže `/clanek/123-neco/jineho` by se naparsovalo jako jediný slug obsahující `/`. Pokud nechcete lomítka ve slugu, zůstaňte u defaultního ``. + +URL se teď parsuje správně, ale generované odkazy slug neobsahují. Dalším krokem je routu naučit, jak slug doplnit. + + +Generování slugu bez zásahu do šablon +===================================== + +Tohle je hlavní varianta. Stávající `n:href="Article:detail, $id"` volání zůstávají beze změny napříč celou aplikací — router si titulek vyhledá sám. + +Použijeme **obecný filtr** pod klíčem prázdného stringu — ten vidí všechny parametry najednou a může slug doplnit: + +```php +use Nette\Routing\Route; +use Nette\Utils\Strings; + +$router->addRoute('clanek/[-]', [ + 'presenter' => 'Article', + 'action' => 'detail', + '' => [ + Route::FilterOut => function (array $params) use ($slugProvider): array { + if (isset($params['id']) && empty($params['slug'])) { + $params['slug'] = $slugProvider->getSlug((int) $params['id']); + } + return $params; + }, + ], +]); +``` + +`FilterOut` se spustí pokaždé, když router **generuje** URL. Pokud slug nebyl předán, filtr titulek dohledá a doplní. + +Slugy můžete nasadit napříč celou aplikací jedinou změnou — jednou definicí routy. Každý odkaz v každé šabloně začne automaticky produkovat `/clanek/123-jak-upect-chleba`. Žádný grep, žádné hledání po šablonách, žádný přehlédnutý case. + + +Cache pro vyhledávání +===================== + +Jedno volání odkazu znamená jeden DB dotaz, ale typická stránka jich má hodně — výpisy, drobečková navigace, „naposledy prohlížené", související články. Stejné ID článku se v rámci jednoho requestu objeví v několika odkazech a nechceme do DB chodit pokaždé. + +Stačí drobná per-request cache. Obalte DB volání malou službou: + +```php +final class SlugProvider +{ + /** @var array */ + private array $cache = []; + + public function __construct( + private Nette\Database\Explorer $db, + ) { + } + + public function getSlug(int $id): string + { + return $this->cache[$id] ??= Strings::webalize(Strings::truncate( + (string) $this->db->fetchField('SELECT title FROM article WHERE id = ?', $id), + 100, '' + )); + } +} +``` + +To stačí — jeden DB dotaz na unikátní ID za request. + + +Předání titulku ze šablony (volitelná rychlá cesta) +=================================================== + +Pokud máte titulek v šabloně po ruce, můžete se DB dotazu úplně vyhnout. Předejte titulek jako pojmenovaný parametr: + +```latte +{$article->title} +``` + +…a přidejte per-parametrový `FilterOut`, který titulek převede na URL-bezpečný tvar: + +```php +$router->addRoute('clanek/[-]', [ + 'presenter' => 'Article', + 'action' => 'detail', + 'slug' => [ + Route::FilterOut => fn($title) => Strings::webalize(Strings::truncate($title, 100, '')), + ], + '' => [/* fallback s vyhledáním z předchozí ukázky */], +]); +``` + +Oba filtry spolupracují. Per-parametrový `FilterOut` proběhne první a předaný titulek převede na slug. Obecný filtr pak vidí, že slug je už vyplněn, a vyhledání v DB přeskočí. Šablony, které titulek nepředávají, dál fungují — projdou cestou s vyhledáváním. + +Použijte to jen tam, kde to opravdu hraje roli (velké výpisy renderované stokrát za request). Pro většinu aplikace cachované vyhledávání stačí. + + +Kanonizace: přesměrování na správnou URL +======================================== + +Umíme teď generovat `/clanek/123-jak-upect-chleba`, ale routa pořád přijímá `/clanek/123` i `/clanek/123-cokoli-co-nekdo-napsal`. To je záměr — chceme krátké URL (viz níže) a chceme, aby staré nebo ručně napsané odkazy fungovaly. Ale nechceme, aby vyhledávače indexovaly stejný článek pod několika adresami. + +Řešením je [kanonizace |application:presenters#kanonizace]: když uživatel přijde po nekanonické URL, aplikace ho přesměruje 301 na správnou. Stará se o to metoda `canonicalize()`: + +```php +public function actionDetail(int $id, ?string $slug = null): void +{ + $article = $this->facade->getArticle($id); + if (!$article) { + $this->error(); + } + + // vygeneruje kanonickou URL přes stejný FilterOut + // a pokud se liší od současné URL, přesměruje HTTP 301 + $this->canonicalize('detail', ['id' => $id]); + + $this->template->article = $article; +} +``` + +`canonicalize()` vygeneruje kanonickou URL stejným způsobem jako `link()` (takže projde stejným `FilterOut`) a porovná ji s aktuální URL. Pokud se liší, přesměruje HTTP 301. Návštěvník skončí na správné URL, vyhledávače vidí jen jednu kanonickou verzi. + + +Jedno místo, které určuje, jak slug vypadá +========================================== + +Všimněte si, že `Strings::webalize(Strings::truncate(..., 100, ''))` žije na **jediném místě** — uvnitř `SlugProvider` (nebo v per-parametrovém `FilterOut`). Stejná logika vyrobí odkaz v šabloně, URL v `redirect()` i kanonický tvar v `canonicalize()`. + +Když budete chtít pravidla později změnit (jiný limit délky, jiná transliterace, vyhazování dalších znaků), upravíte jeden řádek. Bez tohoto byste riskovali, že `redirect()` vygeneruje `/clanek/123-jak-upect-chleba`, zatímco `canonicalize()` bude očekávat `/clanek/123-jak-upect-chl` (protože někde někdo použil jiný `truncate`), a aplikace by se přesměrovávala donekonečna. + + +Bonus: krátké URL stále fungují +=============================== + +Protože je slug nepovinný, fungují i adresy bez něj: + +``` +/clanek/123 +``` + +To se hodí pro: +- **QR kódy** — kratší URL znamená méně hustý a lépe skenovatelný kód +- **SMS a chat** — vejde se do tweetu, vypadá úhledně +- **Tištěné materiály** — krátkou URL se rychleji napíše + +Když uživatel takovou URL otevře, `canonicalize()` ho přesměruje 301 na plnou verzi se slugem, takže vyhledávače stejně uvidí jen kanonický tvar. Můžete mít krátkost i SEO zároveň. + + +Shrnutí +======= + +- Maska `[-]` dělá slug nepovinným. Defaultní `` nematchuje `/`; `` použijte jen tehdy, když opravdu chcete lomítka ve slugu. +- Obecný `FilterOut` pod klíčem `''` dohledá titulek podle ID — **bez zásahu do šablon kdekoli v aplikaci**. +- Vyhledávání obalte drobnou per-request cache; jeden DB dotaz na unikátní ID stačí. +- Volitelně může per-parametrový `FilterOut` umožnit šablonám titulek předat přímo a vyhledávání přeskočit. +- `$this->canonicalize()` v action přesměruje nekanonické URL na správnou s HTTP 301. +- Vzorec pro slug (`webalize` + `truncate`) žije na jednom místě — změníte ho jednou, projeví se všude. +- Krátké URL jen s ID dál fungují, což se hodí pro QR kódy a SMS. + +Více o filtrech a kanonizaci najdete v dokumentaci [routování |application:routing#obecne-filtry] a [presenterů |application:presenters#kanonizace]. diff --git a/best-practices/en/@home.texy b/best-practices/en/@home.texy index 04508b55aa..d41a3f0212 100644 --- a/best-practices/en/@home.texy +++ b/best-practices/en/@home.texy @@ -19,6 +19,7 @@ Nette Application - [Dynamic Snippets |dynamic-snippets] - [How to Use the #Requires Attribute |attribute-requires] - [How to Properly Use POST Links |post-links] +- [Pretty URLs with Slugs |pretty-urls]
diff --git a/best-practices/en/creating-editing-form.texy b/best-practices/en/creating-editing-form.texy index 27e0af202b..86003dffc6 100644 --- a/best-practices/en/creating-editing-form.texy +++ b/best-practices/en/creating-editing-form.texy @@ -130,7 +130,6 @@ We store the record in the `$record` property, making it available in the `creat $this->facade->update($id, $data); // ... } -} ``` However, and this should be **the most important takeaway from the entire code**, we must ensure the action is indeed `edit` when creating the form. Otherwise, the verification in the `actionEdit()` method would not occur at all! diff --git a/best-practices/en/editors-and-tools.texy b/best-practices/en/editors-and-tools.texy index c6109dcbab..e81b575eb6 100644 --- a/best-practices/en/editors-and-tools.texy +++ b/best-practices/en/editors-and-tools.texy @@ -13,9 +13,9 @@ We strongly recommend using a full-featured IDE for development, like PhpStorm, **NetBeans IDE** has built-in support for Nette, Latte, and NEON. **PhpStorm**: Install these plugins via `Settings > Plugins > Marketplace`: -- Nette framework helpers -- Latte -- NEON support +- [Nette |https://plugins.jetbrains.com/plugin/28342-nette] +- [Latte |https://plugins.jetbrains.com/plugin/24218-latte-support] or [Latte Pro |https://plugins.jetbrains.com/plugin/19661-latte-pro] +- [NEON |https://plugins.jetbrains.com/plugin/28338-neon] or [NEON / Nette support |https://plugins.jetbrains.com/plugin/18387-neon-nette-support] - Nette Tester **VS Code**: Find the "Nette Latte + Neon" plugin in the marketplace. diff --git a/best-practices/en/lets-create-contact-form.texy b/best-practices/en/lets-create-contact-form.texy index 0fd677048d..695985f569 100644 --- a/best-practices/en/lets-create-contact-form.texy +++ b/best-practices/en/lets-create-contact-form.texy @@ -48,9 +48,6 @@ Let's render the `contactForm` component in the `Home/default.latte` template: For sending the email itself, we'll create a new class named `ContactFacade` and place it in the file `app/Model/ContactFacade.php`: ```php -addRoute('article/[-]', 'Article:detail'); +``` + +The mask `[-]` says: there may be a hyphen and a slug after the ID, but it's not required. The route accepts both `/article/123` and `/article/123-anything`. + +A note on the parameter ``: by default it matches any characters **except a slash** — exactly what we want. If you write ``, the parameter will match slashes too, so `/article/123-something/else` would parse as a single slug containing `/`. Stay with the default `` unless you really need that. + +So far the URL is parsed correctly, but generated links won't contain the slug. The next step is to teach the route how to fill the slug in. + + +Generating the Slug Without Touching Templates +============================================== + +This is the killer variant. Existing `n:href="Article:detail, $id"` calls keep working unchanged across the whole application — the router looks the title up by itself. + +We do this with a **general filter** under the empty-string key — it sees all parameters at once and can add the slug: + +```php +use Nette\Routing\Route; +use Nette\Utils\Strings; + +$router->addRoute('article/[-]', [ + 'presenter' => 'Article', + 'action' => 'detail', + '' => [ + Route::FilterOut => function (array $params) use ($slugProvider): array { + if (isset($params['id']) && empty($params['slug'])) { + $params['slug'] = $slugProvider->getSlug((int) $params['id']); + } + return $params; + }, + ], +]); +``` + +`FilterOut` runs every time the router **generates** a URL. If the slug wasn't passed in, the filter looks the title up and adds it. + +You can deploy slugs across a whole application in a single change — just one route definition. Every link in every template starts producing `/article/123-how-to-bake-bread` automatically. No grep, no template hunt, no missed corner case. + + +Cache the Lookup +================ + +One link generates one DB query, but a typical page has many — listings, breadcrumbs, "last viewed", related articles. The same article ID often appears in several links during a single request, and you don't want to hit the database every time. + +A tiny per-request cache solves this. Wrap the DB call in a small service: + +```php +final class SlugProvider +{ + /** @var array */ + private array $cache = []; + + public function __construct( + private Nette\Database\Explorer $db, + ) { + } + + public function getSlug(int $id): string + { + return $this->cache[$id] ??= Strings::webalize(Strings::truncate( + (string) $this->db->fetchField('SELECT title FROM article WHERE id = ?', $id), + 100, '' + )); + } +} +``` + +That's enough — one DB hit per unique ID per request. + + +Passing the Title from the Template (Optional Fast Path) +======================================================== + +When the title is already at hand in the template, you can skip the DB lookup entirely. Pass the title as a named parameter: + +```latte +{$article->title} +``` + +…and add a per-parameter `FilterOut` that turns the title into a URL-safe string: + +```php +$router->addRoute('article/[-]', [ + 'presenter' => 'Article', + 'action' => 'detail', + 'slug' => [ + Route::FilterOut => fn($title) => Strings::webalize(Strings::truncate($title, 100, '')), + ], + '' => [/* the lookup-fallback from above */], +]); +``` + +The two filters cooperate. The per-parameter `FilterOut` runs first and turns the supplied title into a slug. The general filter then sees the slug is already filled and skips the DB lookup. Templates that don't pass the title still work — they go through the lookup path. + +Use this only where it matters (large listings rendered hundreds of times per request). For most of the application the cached lookup is fast enough. + + +Canonization: Redirect to the Right URL +======================================= + +We can now generate `/article/123-how-to-bake-bread`, but the route still accepts `/article/123` and `/article/123-anything-someone-wrote`. That's deliberate — we want short URLs (more on that below) and we want old or hand-typed links to keep working. But we don't want search engines to index the same article under multiple addresses. + +The solution is [canonization |application:presenters#canonization]: when the user arrives via a non-canonical URL, the application 301-redirects them to the correct one. The `canonicalize()` method handles this: + +```php +public function actionDetail(int $id, ?string $slug = null): void +{ + $article = $this->facade->getArticle($id); + if (!$article) { + $this->error(); + } + + // generates the canonical URL through the same FilterOut + // and redirects with HTTP 301 if it differs from the current URL + $this->canonicalize('detail', ['id' => $id]); + + $this->template->article = $article; +} +``` + +`canonicalize()` generates the canonical URL the same way `link()` would (so it runs through the same `FilterOut`) and compares it to the current URL. If they differ, it redirects with HTTP 301. Visitors land on the right URL, search engines see only one canonical version. + + +One Place That Decides What the Slug Looks Like +=============================================== + +Notice that the `Strings::webalize(Strings::truncate(..., 100, ''))` call lives in a single place — inside `SlugProvider` (or the per-parameter `FilterOut`). The same logic produces the link in the template, the URL in `redirect()`, and the canonical form in `canonicalize()`. + +If you want to change the rules later (different length limit, different transliteration, stripping extra characters), you change one line. Without this, you'd risk `redirect()` generating `/article/123-how-to-bake-bread` while `canonicalize()` expects `/article/123-how-to-bake-bre` (because someone applied a different `truncate` length elsewhere), and the application would redirect in a loop. + + +Bonus: Short URLs Still Work +============================ + +Because the slug is optional, addresses without it still work: + +``` +/article/123 +``` + +This is useful for: +- **QR codes** — shorter URL means a less dense, more scannable code +- **SMS and chat** — fits in a tweet, looks tidy +- **Printed materials** — a short URL is faster to type + +When a user opens such a URL, `canonicalize()` 301-redirects them to the full version with the slug, so search engines still see only the canonical form. You can have shortness and SEO at the same time. + + +Summary +======= + +- Mask `[-]` makes the slug optional. The default `` doesn't match `/`; use `` only if you really want slashes in the slug. +- A general `FilterOut` under the `''` key looks the title up by ID — **no template changes anywhere in the application**. +- Wrap the lookup in a tiny per-request cache; one DB query per unique ID is plenty. +- Optionally, a per-parameter `FilterOut` lets templates pass the title directly and skip the lookup. +- `$this->canonicalize()` in the action redirects non-canonical URLs to the right one with HTTP 301. +- The slug formula (`webalize` + `truncate`) lives in one place — change it once, take effect everywhere. +- Short ID-only URLs keep working, which is handy for QR codes and SMS. + +You'll find more about filters and canonization in the [routing |application:routing#general-filters] and [presenters |application:presenters#canonization] documentation. diff --git a/bootstrap/en/@home.texy b/bootstrap/en/@home.texy index 0e80ab16c2..db6b6c6d68 100644 --- a/bootstrap/en/@home.texy +++ b/bootstrap/en/@home.texy @@ -93,4 +93,8 @@ $configurator->addDynamicParameters([ ]); ``` +<<<<<<< patch-2 +The `remoteIp` parameter can be referenced in the configuration using the `%remoteIp%` notation. +======= The `projectId` parameter can be referenced in the configuration using the `%projectId%` notation. +>>>>>>> master diff --git a/caching/cs/@home.texy b/caching/cs/@home.texy index 17ddcaa49f..1d3f3eed17 100644 --- a/caching/cs/@home.texy +++ b/caching/cs/@home.texy @@ -125,7 +125,7 @@ Nebo pomocí 3. parametru v metodě `load()`, např: ```php $value = $cache->load($key, function () { - return ...; + return /* ... */; }, [Cache::Expire => '20 minutes']); ``` @@ -293,7 +293,7 @@ Velmi elegantně lze zachytávat a cachovat výstup: ```php if ($capture = $cache->capture($key)) { - echo ... // vypisujeme data + // echo ... vypisujeme data $capture->end(); // uložíme výstup do cache } diff --git a/caching/en/@home.texy b/caching/en/@home.texy index d8a7533643..e3557a0539 100644 --- a/caching/en/@home.texy +++ b/caching/en/@home.texy @@ -125,7 +125,7 @@ Or by using the 3rd parameter of the `load()` method itself, e.g.: ```php $value = $cache->load($key, function () { - return ...; + return /* ... */; }, [Cache::Expire => '20 minutes']); ``` @@ -293,7 +293,7 @@ Output can be captured and cached very elegantly: ```php if ($capture = $cache->capture($key)) { - echo ... // printing some data + // echo ... printing some data $capture->end(); // save the output to the cache } diff --git a/contributing/cs/coding-standard.texy b/contributing/cs/coding-standard.texy index aaef617655..5bac29274f 100644 --- a/contributing/cs/coding-standard.texy +++ b/contributing/cs/coding-standard.texy @@ -14,7 +14,7 @@ Obecná pravidla - Dva prázdné řádky se používají k oddělení metod pro lepší čitelnost. - Důvod použití shut-up operátoru musí být zdokumentován: `@mkdir($dir); // @ - adresář může existovat`. - Pokud je použit slabě typizovaný operátor porovnání (tj. `==`, `!=`, ...), musí být zdokumentován záměr: `// == přijmout null` -- Do jednoho souboru `exceptions.php` můžete zapsat více výjimek. +- Do jednoho souboru `exceptions.php` můžete zapsat více výjimek, do souboru `enums.php` více enumů. - U rozhraní se nespecifikuje viditelnost metod, protože jsou vždy veřejné. - Každá property, návratová hodnota a parametr musí mít uvedený typ. Naopak u finálních konstant typ nikdy neuvádíme, protože je zjevný. - K ohraničení řetězce by se měly používat jednoduché uvozovky, s výjimkou případů, kdy samotný literál obsahuje apostrofy. diff --git a/contributing/en/coding-standard.texy b/contributing/en/coding-standard.texy index df9a0a86cb..2883d9634d 100644 --- a/contributing/en/coding-standard.texy +++ b/contributing/en/coding-standard.texy @@ -14,7 +14,7 @@ General Rules - Two empty lines are used to separate methods for better readability - The reason for using the shut-up operator (`@`) must be documented: `@mkdir($dir); // @ - directory may exist` - If a weak typed comparison operator is used (i.e., `==`, `!=`, ...), the intention must be documented: `// == to accept null` -- You can write multiple exception classes into a single file named `exceptions.php` +- You can write multiple exception classes into a single file named `exceptions.php`, and multiple enums into `enums.php` - The visibility of methods is not specified for interfaces because they are always public - Each property, return value, and parameter must have a type specified. Conversely, for final constants, we never specify the type because it is obvious - Single quotes should be used to delimit strings, except when the literal itself contains apostrophes diff --git a/database/bg/@left-menu.texy b/database/bg/@left-menu.texy index 928b1f73ea..d0fed337a9 100644 --- a/database/bg/@left-menu.texy +++ b/database/bg/@left-menu.texy @@ -6,7 +6,7 @@ Nette Database - [Трансакции |transactions] - [Изключения |exceptions] - [Рефлексия |reflection] -- [Мапинг |mapping] +- [Мапинг |type-conversion] - [Конфигурация |configuration] - [Рискове за сигурността |security] - [Надграждане |en:upgrading] diff --git a/database/bg/mapping.texy b/database/bg/type-conversion.texy similarity index 100% rename from database/bg/mapping.texy rename to database/bg/type-conversion.texy diff --git a/database/cs/@left-menu.texy b/database/cs/@left-menu.texy index 6cb0892efa..1f70db2700 100644 --- a/database/cs/@left-menu.texy +++ b/database/cs/@left-menu.texy @@ -6,7 +6,7 @@ Nette Database - [Transakce |transactions] - [Výjimky |exceptions] - [Reflexe |reflection] -- [Mapování |mapping] +- [Konverze typů |type-conversion] - [Konfigurace |configuration] - [Bezpečnostní rizika |security] - [Upgrade |upgrading] diff --git a/database/cs/exceptions.texy b/database/cs/exceptions.texy index e78dd0108d..f06d44a561 100644 --- a/database/cs/exceptions.texy +++ b/database/cs/exceptions.texy @@ -10,11 +10,14 @@ Nette Database používá hierarchii výjimek. Základní třídou je `Nette\Dat Z `DriverException` dědí následující specializované výjimky: - `ConnectionException` - signalizuje selhání připojení k databázovému serveru + - `ConnectionLostException` .{data-version:3.2.9} - spojení bylo ztraceno během operace (restart serveru, výpadek sítě, idle timeout); před dalším použitím je potřeba se znovu připojit - `ConstraintViolationException` - základní třída pro porušení databázových omezení, ze které dědí: - `ForeignKeyConstraintViolationException` - porušení cizího klíče - `NotNullConstraintViolationException` - porušení NOT NULL omezení - `UniqueConstraintViolationException` - porušení unikátnosti hodnoty - + - `CheckConstraintViolationException` .{data-version:3.2.9} - porušení CHECK omezení +- `DeadlockException` .{data-version:3.2.9} - deadlock nebo serializační konflikt zjištěný serverem; transakce byla zrušena a operaci lze zopakovat +- `LockTimeoutException` .{data-version:3.2.9} - vypršel časový limit při čekání na zámek; příkaz byl přerušen, okolní transakce obvykle zůstává otevřená Příklad zachytávání výjimky `UniqueConstraintViolationException`, která nastane, když se snažíme vložit uživatele s emailem, který už v databázi existuje (za předpokladu, že sloupec email má unikátní index). diff --git a/database/cs/explorer.texy b/database/cs/explorer.texy index 24044ef827..646e033f48 100644 --- a/database/cs/explorer.texy +++ b/database/cs/explorer.texy @@ -263,7 +263,7 @@ Usnadňuje stránkování výsledků. Přijímá číslo stránky (počítané o ```php $numOfPages = null; -$table->page(page: 3, itemsPerPage: 10, $numOfPages); +$table->page(page: 3, itemsPerPage: 10, numOfPages: $numOfPages); echo "Celkem stránek: $numOfPages"; ``` diff --git a/database/cs/mapping.texy b/database/cs/type-conversion.texy similarity index 100% rename from database/cs/mapping.texy rename to database/cs/type-conversion.texy diff --git a/database/de/@left-menu.texy b/database/de/@left-menu.texy index a174fb97e9..b52d2535b7 100644 --- a/database/de/@left-menu.texy +++ b/database/de/@left-menu.texy @@ -6,7 +6,7 @@ Nette Database - [Transaktionen |transactions] - [Ausnahmen |exceptions] - [Reflexion |reflection] -- [Mapping |mapping] +- [Mapping |type-conversion] - [Konfiguration |configuration] - [Sicherheitsrisiken |security] - [Upgrade |en:upgrading] diff --git a/database/de/mapping.texy b/database/de/type-conversion.texy similarity index 100% rename from database/de/mapping.texy rename to database/de/type-conversion.texy diff --git a/database/el/@left-menu.texy b/database/el/@left-menu.texy index 2bfe03617b..d6e6721261 100644 --- a/database/el/@left-menu.texy +++ b/database/el/@left-menu.texy @@ -6,7 +6,7 @@ Nette Database - [Συναλλαγές |transactions] - [Εξαιρέσεις |exceptions] - [Reflection |reflection] -- [Αντιστοίχιση |mapping] +- [Αντιστοίχιση |type-conversion] - [Διαμόρφωση |configuration] - [Κίνδυνοι ασφαλείας |security] - [Αναβάθμιση |en:upgrading] diff --git a/database/el/mapping.texy b/database/el/type-conversion.texy similarity index 100% rename from database/el/mapping.texy rename to database/el/type-conversion.texy diff --git a/database/en/@left-menu.texy b/database/en/@left-menu.texy index f865f8027e..83c1eb8a7a 100644 --- a/database/en/@left-menu.texy +++ b/database/en/@left-menu.texy @@ -6,7 +6,7 @@ Nette Database - [Transactions] - [Exceptions] - [Reflection] -- [Mapping] +- [Type Conversion |type-conversion] - [Configuration] - [Security Risks |security] - [Upgrading] diff --git a/database/en/exceptions.texy b/database/en/exceptions.texy index d3780dcacc..dca25c556e 100644 --- a/database/en/exceptions.texy +++ b/database/en/exceptions.texy @@ -10,11 +10,14 @@ Nette Database uses an exception hierarchy. The base class is `Nette\Database\Dr The `DriverException` class is extended by the following specialized exceptions: - `ConnectionException` – indicates a failure to connect to the database server. + - `ConnectionLostException` .{data-version:3.2.9} – the connection was dropped during an operation (server restart, network failure, idle timeout); a reconnect is required before further use. - `ConstraintViolationException` – the base class for database constraint violations, from which the following exceptions inherit: - `ForeignKeyConstraintViolationException` – violation of a foreign key constraint. - `NotNullConstraintViolationException` – violation of a NOT NULL constraint. - `UniqueConstraintViolationException` – violation of a uniqueness constraint. - + - `CheckConstraintViolationException` .{data-version:3.2.9} – violation of a CHECK constraint. +- `DeadlockException` .{data-version:3.2.9} – a deadlock or serialization failure detected by the server; the transaction was rolled back and may be retried. +- `LockTimeoutException` .{data-version:3.2.9} – a lock-wait timeout was exceeded; the statement was aborted, but the surrounding transaction typically remains open. The following example demonstrates how to catch a `UniqueConstraintViolationException`, which occurs when trying to insert a user with an email that already exists in the database (assuming the `email` column has a unique index): diff --git a/database/en/explorer.texy b/database/en/explorer.texy index dc5e7ef7af..d08f43a51e 100644 --- a/database/en/explorer.texy +++ b/database/en/explorer.texy @@ -263,7 +263,7 @@ Facilitates pagination of results. It accepts the page number (starting from 1) ```php $numOfPages = null; -$table->page(page: 3, itemsPerPage: 10, $numOfPages); +$table->page(page: 3, itemsPerPage: 10, numOfPages: $numOfPages); echo "Total pages: $numOfPages"; ``` diff --git a/database/en/mapping.texy b/database/en/type-conversion.texy similarity index 100% rename from database/en/mapping.texy rename to database/en/type-conversion.texy diff --git a/database/es/@left-menu.texy b/database/es/@left-menu.texy index 334a1f0ecd..ac119a7204 100644 --- a/database/es/@left-menu.texy +++ b/database/es/@left-menu.texy @@ -6,7 +6,7 @@ Nette Database - [Transacciones |transactions] - [Excepciones |exceptions] - [Reflexión |reflection] -- [Mapeo |mapping] +- [Mapeo |type-conversion] - [Configuración |configuration] - [Riesgos de seguridad |security] - [Actualización |en:upgrading] diff --git a/database/es/mapping.texy b/database/es/type-conversion.texy similarity index 100% rename from database/es/mapping.texy rename to database/es/type-conversion.texy diff --git a/database/fr/@left-menu.texy b/database/fr/@left-menu.texy index bf2b375e4d..b54d7daab7 100644 --- a/database/fr/@left-menu.texy +++ b/database/fr/@left-menu.texy @@ -6,7 +6,7 @@ Nette Database - [Transactions |transactions] - [Exceptions |exceptions] - [Réflexion |reflection] -- [Mapping |mapping] +- [Mapping |type-conversion] - [Configuration |configuration] - [Risques de sécurité |security] - [Mise à niveau |en:upgrading] diff --git a/database/fr/mapping.texy b/database/fr/type-conversion.texy similarity index 100% rename from database/fr/mapping.texy rename to database/fr/type-conversion.texy diff --git a/database/hu/@left-menu.texy b/database/hu/@left-menu.texy index 6bf3ca54b9..2b9f11fd5e 100644 --- a/database/hu/@left-menu.texy +++ b/database/hu/@left-menu.texy @@ -6,7 +6,7 @@ Nette Database - [Tranzakciók |transactions] - [Kivételek |exceptions] - [Reflexió |reflection] -- [Leképezés |mapping] +- [Leképezés |type-conversion] - [Konfiguráció |configuration] - [Biztonsági kockázatok |security] - [Frissítés |en:upgrading] diff --git a/database/hu/mapping.texy b/database/hu/type-conversion.texy similarity index 100% rename from database/hu/mapping.texy rename to database/hu/type-conversion.texy diff --git a/database/it/@left-menu.texy b/database/it/@left-menu.texy index 58127bb77b..421ffa9bce 100644 --- a/database/it/@left-menu.texy +++ b/database/it/@left-menu.texy @@ -6,7 +6,7 @@ Nette Database - [Transazioni |transactions] - [Eccezioni |exceptions] - [Riflessione |reflection] -- [Mappatura |mapping] +- [Mappatura |type-conversion] - [Configurazione |configuration] - [Rischi per la sicurezza |security] - [Aggiornamento |en:upgrading] diff --git a/database/it/mapping.texy b/database/it/type-conversion.texy similarity index 100% rename from database/it/mapping.texy rename to database/it/type-conversion.texy diff --git a/database/ja/@left-menu.texy b/database/ja/@left-menu.texy index 4b7d6758d8..0103373e5c 100644 --- a/database/ja/@left-menu.texy +++ b/database/ja/@left-menu.texy @@ -6,7 +6,7 @@ Nette Database - [トランザクション |transactions] - [例外 |exceptions] - [リフレクション |reflection] -- [マッピング |mapping] +- [マッピング |type-conversion] - [設定 |configuration] - [セキュリティリスク |security] - [アップグレード |en:upgrading] diff --git a/database/ja/mapping.texy b/database/ja/type-conversion.texy similarity index 100% rename from database/ja/mapping.texy rename to database/ja/type-conversion.texy diff --git a/database/pl/@left-menu.texy b/database/pl/@left-menu.texy index 635d04210c..65bd87f505 100644 --- a/database/pl/@left-menu.texy +++ b/database/pl/@left-menu.texy @@ -6,7 +6,7 @@ Nette Database - [Transakcje |transactions] - [Wyjątki |exceptions] - [Refleksja |reflection] -- [Mapowanie |mapping] +- [Mapowanie |type-conversion] - [Konfiguracja |configuration] - [Zagrożenia bezpieczeństwa |security] - [Aktualizacja |en:upgrading] diff --git a/database/pl/mapping.texy b/database/pl/type-conversion.texy similarity index 100% rename from database/pl/mapping.texy rename to database/pl/type-conversion.texy diff --git a/database/pt/@left-menu.texy b/database/pt/@left-menu.texy index b1a45ab3c0..2d2896d66b 100644 --- a/database/pt/@left-menu.texy +++ b/database/pt/@left-menu.texy @@ -6,7 +6,7 @@ Nette Database - [Transações |transactions] - [Exceções |exceptions] - [Reflexão |reflection] -- [Mapeamento |mapping] +- [Mapeamento |type-conversion] - [Configuração |configuration] - [Riscos de segurança |security] - [Atualização |en:upgrading] diff --git a/database/pt/mapping.texy b/database/pt/type-conversion.texy similarity index 100% rename from database/pt/mapping.texy rename to database/pt/type-conversion.texy diff --git a/database/ro/@left-menu.texy b/database/ro/@left-menu.texy index 7e37400986..db1ab4a266 100644 --- a/database/ro/@left-menu.texy +++ b/database/ro/@left-menu.texy @@ -6,7 +6,7 @@ Nette Database - [Tranzacții |transactions] - [Excepții |exceptions] - [Reflecție |reflection] -- [Mapare |mapping] +- [Mapare |type-conversion] - [Configurație |configuration] - [Riscuri de securitate |security] - [Actualizare |en:upgrading] diff --git a/database/ro/mapping.texy b/database/ro/type-conversion.texy similarity index 100% rename from database/ro/mapping.texy rename to database/ro/type-conversion.texy diff --git a/database/ru/@left-menu.texy b/database/ru/@left-menu.texy index 91864ddc35..88521f43d7 100644 --- a/database/ru/@left-menu.texy +++ b/database/ru/@left-menu.texy @@ -6,7 +6,7 @@ Nette Database - [Транзакции |transactions] - [Исключения |exceptions] - [Рефлексия |reflection] -- [Маппинг |mapping] +- [Маппинг |type-conversion] - [Конфигурация |configuration] - [Риски безопасности |security] - [Обновление |en:upgrading] diff --git a/database/ru/mapping.texy b/database/ru/type-conversion.texy similarity index 100% rename from database/ru/mapping.texy rename to database/ru/type-conversion.texy diff --git a/database/sl/@left-menu.texy b/database/sl/@left-menu.texy index 280bbea034..0a5b5c42b2 100644 --- a/database/sl/@left-menu.texy +++ b/database/sl/@left-menu.texy @@ -6,7 +6,7 @@ Nette Database - [Transakcije |transactions] - [Izjeme |exceptions] - [Refleksija |reflection] -- [Preslikava |mapping] +- [Preslikava |type-conversion] - [Konfiguracija |configuration] - [Varnostna tveganja |security] - [Nadgradnja |en:upgrading] diff --git a/database/sl/mapping.texy b/database/sl/type-conversion.texy similarity index 100% rename from database/sl/mapping.texy rename to database/sl/type-conversion.texy diff --git a/database/tr/@left-menu.texy b/database/tr/@left-menu.texy index 5a4cd8355f..ac89e59cd1 100644 --- a/database/tr/@left-menu.texy +++ b/database/tr/@left-menu.texy @@ -6,7 +6,7 @@ Nette Database - [İşlemler |transactions] - [İstisnalar |exceptions] - [Yansıma |reflection] -- [Eşleme |mapping] +- [Eşleme |type-conversion] - [Yapılandırma |configuration] - [Güvenlik Riskleri |security] - [Yükseltme |en:upgrading] diff --git a/database/tr/mapping.texy b/database/tr/type-conversion.texy similarity index 100% rename from database/tr/mapping.texy rename to database/tr/type-conversion.texy diff --git a/database/uk/@left-menu.texy b/database/uk/@left-menu.texy index f8a09afcd7..8425fd5b2d 100644 --- a/database/uk/@left-menu.texy +++ b/database/uk/@left-menu.texy @@ -6,7 +6,7 @@ Nette Database - [Транзакції |transactions] - [Винятки |exceptions] - [Рефлексія |reflection] -- [Мапування |mapping] +- [Мапування |type-conversion] - [Конфігурація |configuration] - [Ризики безпеки |security] - [Оновлення |en:upgrading] diff --git a/database/uk/mapping.texy b/database/uk/type-conversion.texy similarity index 100% rename from database/uk/mapping.texy rename to database/uk/type-conversion.texy 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/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/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 '' . $imageAlt . ''; На нападателя е достатъчно като описание да вмъкне умело съставен низ `" onload="alert('Hacked!')` и ако изписването не е обработено, резултатният код ще изглежда така: -```html +```latte ``` @@ -91,7 +91,7 @@ echo '' . $imageAlt . ''; Какво точно се разбира под думата контекст? Това е място в документа със собствени правила за обработка на извежданите данни. Зависи от типа на документа (HTML, XML, CSS, JavaScript, plain text, ...) и може да се различава в конкретните му части. Например в HTML документ има цяла редица такива места (контексти), където важат много различни правила. Може би ще се изненадате колко са. Ето първите четири: -```html +```latte

#текст

@@ -108,7 +108,7 @@ echo '' . $imageAlt . ''; Контекстите също могат да се наслояват, което се случва, когато вмъкнем JavaScript или CSS в HTML. Това може да се направи по два различни начина, с елемент и с атрибут: -```html +```latte @@ -132,7 +132,7 @@ echo '' . $imageAlt . ''; Ако го извеждате в HTML текст, точно в този случай не е необходимо да правите никакви замени, защото низът не съдържа нито един знак със специално значение. Друга ситуация възниква, ако го изведете вътре в HTML атрибут, ограден с единични кавички. В такъв случай е необходимо да екранирате кавичките в HTML ентичности: -```html +```latte
``` @@ -152,13 +152,13 @@ alert('Rock\'n\'Roll'); Ако този код вмъкнем в HTML документ с помощта на ` ``` Ако обаче искахме да го вмъкнем в 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 foo ``` @@ -330,7 +330,7 @@ Latte вижда шаблона по същия начин като вас. Ра Latte вижда шаблона по същия начин като вас. За разлика от Twig, разбира HTML и знае, че променливата се извежда като стойност на атрибут, който не е в кавички. Затова ги допълва. Когато нападателят вмъкне същото описание, резултатният код ще изглежда така: -```html +```latte foo onload=alert('Hacked!') ``` 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 ``` @@ -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 '

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 +```latte

Výsledky vyhledávání pro

``` @@ -59,7 +59,7 @@ echo '' . $imageAlt . ''; Ú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í ` ``` 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 foo ``` @@ -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 foo onload=alert('Hacked!') ``` 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} +
  • {$item}
  • + {/foreach} +
+``` + +Vypíše: + +```latte +
    +
  • foo
  • +
  • bar
  • +
+``` + +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 +
+ {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 +```latte

Suchergebnisse für

``` @@ -59,7 +59,7 @@ echo '' . $imageAlt . ''; 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 ` ``` 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 foo ``` @@ -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 foo onload=alert('Gehackt!') ``` 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 '' . $imageAlt . ''; Αρκεί ο εισβολέας να εισάγει ως λεζάντα ένα έξυπνα κατασκευασμένο 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 χρησιμοποιώντας το ` ``` Αν όμως θέλαμε να το εισάγουμε σε ένα 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 foo ``` @@ -330,7 +330,7 @@ Latte εναντίον απλοϊκών συστημάτων Το Latte βλέπει το πρότυπο όπως εσείς. Σε αντίθεση με το Twig, καταλαβαίνει HTML και ξέρει ότι η μεταβλητή εκτυπώνεται ως τιμή ενός attribute που δεν βρίσκεται σε εισαγωγικά. Γι' αυτό τα συμπληρώνει. Όταν ο εισβολέας εισάγει την ίδια λεζάντα, ο προκύπτων κώδικας θα μοιάζει ως εξής: -```html +```latte foo onload=alert('Hacked!') ``` 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 ``` @@ -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 '

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 +```latte

Search results for

``` @@ -59,7 +59,7 @@ echo '' . $imageAlt . ''; 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 ` ``` 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 foo ``` @@ -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 foo onload=alert('Hacked!') ``` 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} +
  • {$item}
  • + {/foreach} +
+``` + +Outputs: + +```latte +
    +
  • foo
  • +
  • bar
  • +
+``` + +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 +
+ {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 +```latte

Resultados de la búsqueda para

``` @@ -59,7 +59,7 @@ echo '' . $imageAlt . ''; 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 ` ``` 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 foo ``` @@ -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 foo onload=alert('Hacked!') ``` 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 +```latte

Résultats de la recherche pour

``` @@ -59,7 +59,7 @@ echo '' . $imageAlt . ''; 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 ` ``` 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 foo ``` @@ -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 foo onload=alert('Piraté !') ``` 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 +```latte

Keresési eredmények erre:

``` @@ -59,7 +59,7 @@ echo '' . $imageAlt . ''; 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 ` @@ -108,7 +108,7 @@ Potresti essere sorpreso, ma regole speciali si applicano all'interno degli elem I contesti possono anche essere annidati, cosa che accade quando inseriamo JavaScript o CSS in HTML. Questo può essere fatto in due modi diversi, con un elemento e con un attributo: -```html +```latte @@ -132,7 +132,7 @@ Prendiamo la stringa `Rock'n'Roll`. Se la stampi nel testo HTML, proprio in questo caso non è necessario effettuare alcuna sostituzione, perché la stringa non contiene alcun carattere con significato speciale. La situazione cambia se la stampi all'interno di un attributo HTML racchiuso tra apici singoli. In tal caso, è necessario eseguire l'escaping degli apici in entità HTML: -```html +```latte
``` @@ -152,13 +152,13 @@ alert('Rock\'n\'Roll'); Se inseriamo questo codice nel documento HTML usando ` ``` Se però volessimo inserirlo in un attributo HTML, dobbiamo ancora eseguire l'escaping degli apici in entità HTML: -```html +```latte
``` @@ -170,7 +170,7 @@ https://example.org/?a=Jazz&b=Rock%27n%27Roll E quando stampiamo questa stringa in un attributo, applichiamo ancora l'escaping secondo questo contesto e sostituiamo `&` con `&`: -```html +```latte ``` @@ -314,7 +314,7 @@ Notate che non ci sono virgolette attorno ai valori degli attributi. Il codifica Un aggressore inserisce come didascalia dell'immagine una stringa abilmente costruita `foo onload=alert('Hacked!')`. Sappiamo già che Twig non può riconoscere se la variabile viene stampata nel flusso del testo HTML, all'interno di un attributo, di un commento HTML, ecc., in breve non distingue i contesti. E converte meccanicamente solo i caratteri `< > & ' "` in entità HTML. Quindi il codice risultante sarà simile a questo: -```html +```latte foo ``` @@ -330,7 +330,7 @@ Ora vediamo come Latte gestisce lo stesso template: Latte vede il template come lo vedi tu. A differenza di Twig, capisce HTML e sa che la variabile viene stampata come valore di un attributo che non è tra virgolette. Pertanto le aggiunge. Quando l'aggressore inserisce la stessa didascalia, il codice risultante sarà simile a questo: -```html +```latte foo onload=alert('Hacked!') ``` diff --git a/latte/ja/custom-tags.texy b/latte/ja/custom-tags.texy index a3114b7964..347975a50b 100644 --- a/latte/ja/custom-tags.texy +++ b/latte/ja/custom-tags.texy @@ -923,7 +923,7 @@ class MyLatteExtension extends Extension 生成されたHTML: -```html +```latte 削除 ``` diff --git a/latte/ja/safety-first.texy b/latte/ja/safety-first.texy index 15f0190eb4..1e2c48e606 100644 --- a/latte/ja/safety-first.texy +++ b/latte/ja/safety-first.texy @@ -33,7 +33,7 @@ echo '

検索結果: ' . $search . '

'; 攻撃者は、検索ボックス、ひいては変数 `$search` に任意の文字列、つまり `` のようなHTMLコードを入力できます。出力がサニタイズされていないため、表示されるページの一部になります: -```html +```latte

検索結果:

``` @@ -59,7 +59,7 @@ echo '' . $imageAlt . ''; 攻撃者は、説明として巧妙に作成された文字列 `" onload="alert('Hacked!')` を挿入するだけで、出力がサニタイズされていない場合、結果のコードは次のようになります: -```html +```latte ``` @@ -91,7 +91,7 @@ XSSからどのように防御しますか? コンテキストという言葉で正確には何を意味しますか?それは、出力されるデータのサニタイズに関する独自のルールを持つドキュメント内の場所です。それはドキュメントのタイプ(HTML、XML、CSS、JavaScript、プレーンテキストなど)に依存し、その特定の場所によって異なる場合があります。 例えば、HTMLドキュメントには、非常に異なるルールが適用される多くの場所(コンテキスト)があります。いくつあるか驚くかもしれません。ここに最初の4つがあります: -```html +```latte

#テキスト

@@ -108,7 +108,7 @@ HTMLコメント内では興味深いです。ここでは、エスケープにH コンテキストはネストすることもできます。これは、JavaScriptまたはCSSをHTMLに埋め込むときに発生します。これは2つの異なる方法、要素と属性で行うことができます: -```html +```latte @@ -132,7 +132,7 @@ HTMLコメント内では興味深いです。ここでは、エスケープにH HTMLテキストに出力する場合、この特定のケースでは、文字列に特別な意味を持つ文字が含まれていないため、置換を行う必要はありません。状況は、単一引用符で囲まれたHTML属性内に出力する場合に異なります。その場合、引用符をHTMLエンティティにエスケープする必要があります: -```html +```latte
``` @@ -152,13 +152,13 @@ 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はテンプレートをあなたと同じように見ます。HTML、XML 攻撃者は、画像の説明として巧妙に作成された文字列 `foo onload=alert('Hacked!')` を挿入します。Twigは、変数がHTMLテキストの流れの中、属性内、HTMLコメント内など、どこに出力されているかを判断できないこと、つまりコンテキストを区別しないことをすでに知っています。そして、文字 `< > & ' "` を機械的にHTMLエンティティに変換するだけです。 したがって、結果のコードは次のようになります: -```html +```latte foo ``` @@ -330,7 +330,7 @@ Latteはテンプレートをあなたと同じように見ます。HTML、XML Latteはテンプレートをあなたと同じように見ます。Twigとは異なり、HTMLを理解し、変数が引用符で囲まれていない属性の値として出力されていることを知っています。したがって、それらを補完します。攻撃者が同じ説明を挿入すると、結果のコードは次のようになります: -```html +```latte foo onload=alert('Hacked!') ``` diff --git a/latte/pl/custom-tags.texy b/latte/pl/custom-tags.texy index 50be9f0562..8dd42d7929 100644 --- a/latte/pl/custom-tags.texy +++ b/latte/pl/custom-tags.texy @@ -923,7 +923,7 @@ Teraz możesz użyć `n:confirm` na linkach, przyciskach lub elementach formular Wygenerowany HTML: -```html +```latte Usuń ``` diff --git a/latte/pl/safety-first.texy b/latte/pl/safety-first.texy index dc1f49265c..d8a666e1d7 100644 --- a/latte/pl/safety-first.texy +++ b/latte/pl/safety-first.texy @@ -33,7 +33,7 @@ echo '

Wyniki wyszukiwania dla ' . $search . '

'; Atakujący może w polu wyszukiwania, a tym samym w zmiennej `$search`, wpisać dowolny ciąg znaków, czyli również kod HTML, taki jak ``. Ponieważ wyjście nie jest w żaden sposób oczyszczone, stanie się częścią wyświetlonej strony: -```html +```latte

Wyniki wyszukiwania dla

``` @@ -59,7 +59,7 @@ echo '' . $imageAlt . ''; Atakującemu wystarczy jako opis wstawić sprytnie skonstruowany ciąg `" onload="alert('Hacked!')`, a jeśli wyświetlanie nie zostanie oczyszczone, wynikowy kod będzie wyglądał tak: -```html +```latte ``` @@ -91,7 +91,7 @@ Escapowanie kontekstowe Co dokładnie oznacza słowo kontekst? Jest to miejsce w dokumencie z własnymi zasadami oczyszczania wyświetlanych danych. Zależy od typu dokumentu (HTML, XML, CSS, JavaScript, plain text, ...) i może się różnić w jego poszczególnych częściach. Na przykład w dokumencie HTML istnieje wiele takich miejsc (kontekstów), gdzie obowiązują bardzo różne zasady. Być może będziesz zaskoczony, ile ich jest. Oto pierwsza czwórka: -```html +```latte

#text

@@ -108,7 +108,7 @@ Ciekawie jest wewnątrz komentarzy HTML. Tutaj bowiem do escapowania nie używa Konteksty mogą się również nakładać, co ma miejsce, gdy wstawiamy JavaScript lub CSS do HTML. Można to zrobić na dwa różne sposoby, elementem i atrybutem: -```html +```latte @@ -132,7 +132,7 @@ Miejmy ciąg `Rock'n'Roll`. Jeśli będziesz go wyświetlać w tekście HTML, akurat w tym przypadku nie trzeba dokonywać żadnych zamian, ponieważ ciąg nie zawiera żadnego znaku o specjalnym znaczeniu. Inna sytuacja nastąpi, jeśli wyświetlisz go wewnątrz atrybutu HTML ujętego w pojedyncze cudzysłowy. W takim przypadku trzeba escapować cudzysłowy na encje HTML: -```html +```latte
``` @@ -152,13 +152,13 @@ alert('Rock\'n\'Roll'); Jeśli ten kod wstawimy do dokumentu HTML za pomocą ` ``` Jeśli jednak chcielibyśmy go wstawić do atrybutu HTML, musimy jeszcze escapować cudzysłowy na encje HTML: -```html +```latte
``` @@ -170,7 +170,7 @@ https://example.org/?a=Jazz&b=Rock%27n%27Roll A kiedy ten ciąg wyświetlimy w atrybucie, jeszcze zastosujemy escapowanie zgodnie z tym kontekstem i zastąpimy `&` na `&`: -```html +```latte ``` @@ -314,7 +314,7 @@ Zwróć uwagę, że wokół wartości atrybutów nie ma cudzysłowów. Koder mó Atakujący jako opis obrazka wstawia sprytnie skonstruowany ciąg `foo onload=alert('Hacked!')`. Już wiemy, że Twig nie może rozpoznać, czy zmienna jest wyświetlana w przepływie tekstu HTML, wewnątrz atrybutu, komentarza HTML itp., krótko mówiąc, nie rozróżnia kontekstów. I tylko mechanicznie konwertuje znaki `< > & ' "` na encje HTML. Więc wynikowy kod będzie wyglądał tak: -```html +```latte foo ``` @@ -330,7 +330,7 @@ Teraz zobaczymy, jak z tym samym szablonem poradzi sobie Latte: Latte widzi szablon tak samo jak Ty. W przeciwieństwie do Twiga rozumie HTML i wie, że zmienna jest wyświetlana jako wartość atrybutu, który nie jest w cudzysłowach. Dlatego je uzupełni. Kiedy atakujący wstawi ten sam opis, wynikowy kod będzie wyglądał tak: -```html +```latte foo onload=alert('Hacked!') ``` diff --git a/latte/pt/custom-tags.texy b/latte/pt/custom-tags.texy index b080e9e20b..1e523ed866 100644 --- a/latte/pt/custom-tags.texy +++ b/latte/pt/custom-tags.texy @@ -923,7 +923,7 @@ Agora você pode usar `n:confirm` em links, botões ou elementos de formulário: HTML gerado: -```html +```latte Excluir ``` diff --git a/latte/pt/safety-first.texy b/latte/pt/safety-first.texy index 049ff1ae25..5e0351605c 100644 --- a/latte/pt/safety-first.texy +++ b/latte/pt/safety-first.texy @@ -33,7 +33,7 @@ echo '

Resultados da pesquisa para ' . $search . '

'; Um atacante pode inserir na caixa de pesquisa e, consequentemente, na variável `$search`, qualquer string, ou seja, também código HTML como ``. Como a saída não é tratada de forma alguma, ela torna-se parte da página exibida: -```html +```latte

Resultados da pesquisa para

``` @@ -59,7 +59,7 @@ echo '' . $imageAlt . ''; Basta ao atacante inserir como legenda uma string habilmente construída `" onload="alert('Hacked!')` e, se a exibição não for tratada, o código resultante será assim: -```html +```latte ``` @@ -91,7 +91,7 @@ Escaping sensível ao contexto O que exatamente se entende pela palavra contexto? É um local no documento com as suas próprias regras para o tratamento dos dados exibidos. Depende do tipo de documento (HTML, XML, CSS, JavaScript, texto simples, ...) e pode diferir nas suas partes específicas. Por exemplo, num documento HTML, existem muitos desses locais (contextos), onde regras muito diferentes se aplicam. Pode surpreender-se com quantos existem. Aqui temos os quatro primeiros: -```html +```latte

#texto

@@ -108,7 +108,7 @@ Pode surpreender-se, mas regras especiais aplicam-se dentro dos elementos `#js-elemento @@ -132,7 +132,7 @@ Tenha a string `Rock'n'Roll`. Se a exibir em texto HTML, neste caso específico não é necessário fazer nenhuma substituição, pois a string não contém nenhum caractere com significado especial. A situação muda se a exibir dentro de um atributo HTML delimitado por aspas simples. Nesse caso, é necessário escapar as aspas para entidades HTML: -```html +```latte
``` @@ -152,13 +152,13 @@ alert('Rock\'n\'Roll'); Se inserirmos este código num documento HTML usando ` ``` No entanto, se quiséssemos inseri-lo num atributo HTML, ainda precisaríamos de escapar as aspas para entidades HTML: -```html +```latte
``` @@ -170,7 +170,7 @@ https://example.org/?a=Jazz&b=Rock%27n%27Roll E quando exibimos esta string num atributo, ainda aplicamos o escaping de acordo com este contexto e substituímos `&` por `&`: -```html +```latte ``` @@ -314,7 +314,7 @@ Observe que não há aspas em torno dos valores dos atributos. O codificador pod Um atacante insere como legenda da imagem uma string habilmente construída `foo onload=alert('Hacked!')`. Já sabemos que o Twig não pode saber se a variável está a ser exibida no fluxo de texto HTML, dentro de um atributo, comentário HTML, etc., em suma, não distingue contextos. E apenas converte mecanicamente os caracteres `< > & ' "` em entidades HTML. Então, o código resultante será assim: -```html +```latte foo ``` @@ -330,7 +330,7 @@ Agora veremos como o Latte lida com o mesmo template: O Latte vê o template da mesma forma que você. Ao contrário do Twig, ele entende HTML e sabe que a variável está a ser exibida como o valor de um atributo que não está entre aspas. Portanto, ele adiciona-as. Quando o atacante insere a mesma legenda, o código resultante será assim: -```html +```latte foo onload=alert('Hacked!') ``` diff --git a/latte/ro/custom-tags.texy b/latte/ro/custom-tags.texy index bc12920a37..3c353caabb 100644 --- a/latte/ro/custom-tags.texy +++ b/latte/ro/custom-tags.texy @@ -923,7 +923,7 @@ Acum puteți utiliza `n:confirm` pe linkuri, butoane sau elemente de formular: HTML generat: -```html +```latte Șterge ``` diff --git a/latte/ro/safety-first.texy b/latte/ro/safety-first.texy index 8140ba61d3..b4948a948d 100644 --- a/latte/ro/safety-first.texy +++ b/latte/ro/safety-first.texy @@ -33,7 +33,7 @@ echo '

Rezultatele căutării pentru ' . $search . '

'; Un atacator poate introduce în câmpul de căutare și, implicit, în variabila `$search`, orice șir, deci și cod HTML precum ``. Deoarece ieșirea nu este tratată în niciun fel, aceasta devine parte a paginii afișate: -```html +```latte

Rezultatele căutării pentru

``` @@ -59,7 +59,7 @@ echo '' . $imageAlt . ''; Atacatorului îi este suficient să introducă ca descriere un șir inteligent construit `" onload="alert('Hacked!')` și dacă afișarea nu este tratată, codul rezultat va arăta astfel: -```html +```latte ``` @@ -91,7 +91,7 @@ Escapare contextuală sensibilă Ce se înțelege exact prin cuvântul context? Este un loc în document cu propriile reguli pentru tratarea datelor afișate. Depinde de tipul documentului (HTML, XML, CSS, JavaScript, plain text, ...) și poate diferi în părțile sale specifice. De exemplu, într-un document HTML există o serie întreagă de astfel de locuri (contexte) unde se aplică reguli foarte diferite. Poate veți fi surprinși câte sunt. Iată primele patru: -```html +```latte

#text

@@ -108,7 +108,7 @@ Interesant este în interiorul comentariilor HTML. Aici, escaparea nu se face fo Contexturile se pot și stratifica, ceea ce se întâmplă când inserăm JavaScript sau CSS în HTML. Acest lucru se poate face în două moduri diferite, prin element și atribut: -```html +```latte @@ -132,7 +132,7 @@ Să avem șirul `Rock'n'Roll`. Dacă îl veți afișa în text HTML, în acest caz particular nu este nevoie de nicio înlocuire, deoarece șirul nu conține niciun caracter cu semnificație specială. Situația se schimbă dacă îl afișați în interiorul unui atribut HTML delimitat de apostrofuri simple. În acest caz, este necesar să escapați apostrofurile în entități HTML: -```html +```latte
``` @@ -152,13 +152,13 @@ alert('Rock\'n\'Roll'); Dacă inserăm acest cod într-un document HTML folosind ` ``` Dacă am dori însă să îl inserăm într-un atribut HTML, trebuie să mai escapăm ghilimelele în entități HTML: -```html +```latte
``` @@ -170,7 +170,7 @@ https://example.org/?a=Jazz&b=Rock%27n%27Roll Și când afișăm acest șir într-un atribut, aplicăm și escaparea conform acestui context și înlocuim `&` cu `&`: -```html +```latte ``` @@ -314,7 +314,7 @@ Observați că în jurul valorilor atributelor nu există ghilimele. Coderul le- Atacatorul introduce ca descriere a imaginii un șir inteligent construit `foo onload=alert('Hacked!')`. Știm deja că Twig nu poate recunoaște dacă variabila se afișează în fluxul textului HTML, în interiorul unui atribut, comentariu HTML etc., pe scurt, nu distinge contextele. Și doar convertesc mecanic caracterele `< > & ' "` în entități HTML. Deci, codul rezultat va arăta astfel: -```html +```latte foo ``` @@ -330,7 +330,7 @@ Acum să vedem cum se descurcă Latte cu același șablon: Latte vede șablonul la fel ca dvs. Spre deosebire de Twig, înțelege HTML și știe că variabila se afișează ca valoare a unui atribut care nu este între ghilimele. De aceea, le completează. Când atacatorul introduce aceeași descriere, codul rezultat va arăta astfel: -```html +```latte foo onload=alert('Hacked!') ``` diff --git a/latte/ru/custom-tags.texy b/latte/ru/custom-tags.texy index dc35fdad01..3c0cd691eb 100644 --- a/latte/ru/custom-tags.texy +++ b/latte/ru/custom-tags.texy @@ -923,7 +923,7 @@ class MyLatteExtension extends Extension Сгенерированный HTML: -```html +```latte Удалить ``` diff --git a/latte/ru/safety-first.texy b/latte/ru/safety-first.texy index 96b91313cc..ffd79d93c4 100644 --- a/latte/ru/safety-first.texy +++ b/latte/ru/safety-first.texy @@ -33,7 +33,7 @@ echo '

Результаты поиска для ' . $search . '

'; Злоумышленник может в поле поиска и, соответственно, в переменную `$search` записать любую строку, в том числе и HTML-код, например ``. Поскольку вывод никак не обрабатывается, он станет частью отображаемой страницы: -```html +```latte

Результаты поиска для

``` @@ -59,7 +59,7 @@ echo '' . $imageAlt . ''; Злоумышленнику достаточно в качестве описания вставить хитро составленную строку `" onload="alert('Hacked!')`, и если вывод не будет обработан, результирующий код будет выглядеть так: -```html +```latte ``` @@ -91,7 +91,7 @@ echo '' . $imageAlt . ''; Что именно подразумевается под словом контекст? Это место в документе со своими правилами обработки выводимых данных. Зависит от типа документа (HTML, XML, CSS, JavaScript, plain text, ...) и может отличаться в его конкретных частях. Например, в HTML-документе таких мест (контекстов), где действуют очень разные правила, целое множество. Возможно, вы удивитесь, сколько их. Вот первая четверка: -```html +```latte

#текст

@@ -108,7 +108,7 @@ echo '' . $imageAlt . ''; Контексты также могут вкладываться, что происходит, когда мы вставляем JavaScript или CSS в HTML. Это можно сделать двумя разными способами, элементом и атрибутом: -```html +```latte @@ -132,7 +132,7 @@ echo '' . $imageAlt . ''; Если вы будете выводить ее в HTML-тексте, то в данном случае не нужно делать никаких замен, потому что строка не содержит ни одного символа со специальным значением. Другая ситуация возникнет, если вы выведете ее внутри HTML-атрибута, заключенного в одинарные кавычки. В таком случае необходимо экранировать кавычки в HTML-сущности: -```html +```latte
``` @@ -152,13 +152,13 @@ alert('Rock\'n\'Roll'); Если этот код вставить в HTML-документ с помощью ` ``` Однако, если бы мы хотели вставить его в 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 foo ``` @@ -330,7 +330,7 @@ Latte видит шаблон так же, как и вы. Он понимает Latte видит шаблон так же, как и вы. В отличие от Twig, он понимает HTML и знает, что переменная выводится как значение атрибута, который не заключен в кавычки. Поэтому он их добавит. Когда злоумышленник вставит то же описание, результирующий код будет выглядеть так: -```html +```latte foo onload=alert('Hacked!') ``` diff --git a/latte/sl/custom-tags.texy b/latte/sl/custom-tags.texy index 1066b5eb86..1bb1570146 100644 --- a/latte/sl/custom-tags.texy +++ b/latte/sl/custom-tags.texy @@ -923,7 +923,7 @@ Zdaj lahko uporabite `n:confirm` na povezavah, gumbih ali elementih obrazca: Generirana HTML koda: -```html +```latte Izbriši ``` diff --git a/latte/sl/safety-first.texy b/latte/sl/safety-first.texy index 509802d6c8..29adc447e2 100644 --- a/latte/sl/safety-first.texy +++ b/latte/sl/safety-first.texy @@ -33,7 +33,7 @@ echo '

Rezultati iskanja za ' . $search . '

'; Napadalec lahko v iskalno polje in posledično v spremenljivko `$search` zapiše poljuben niz, torej tudi HTML kodo kot ``. Ker izpis ni nikakor obdelan, postane del prikazane strani: -```html +```latte

Rezultati iskanja za

``` @@ -59,7 +59,7 @@ echo '' . $imageAlt . ''; Napadalcu zadostuje, da kot opis vstavi spretno sestavljen niz `" onload="alert('Hacked!')` in če izpis ne bo obdelan, bo rezultatna koda izgledala takole: -```html +```latte ``` @@ -91,7 +91,7 @@ Kontekstno občutljivo ubežanje Kaj točno se misli z besedo kontekst? Gre za mesto v dokumentu z lastnimi pravili za obdelavo izpisanih podatkov. Odvisno je od vrste dokumenta (HTML, XML, CSS, JavaScript, navadno besedilo, ...) in se lahko razlikuje v njegovih konkretnih delih. Na primer, v dokumentu HTML je takih mest (kontekstov), kjer veljajo zelo različna pravila, cela vrsta. Morda boste presenečeni, koliko jih je. Tukaj imamo prvo četverico: -```html +```latte

#text

@@ -108,7 +108,7 @@ Zanimivo je znotraj komentarjev HTML. Tukaj se namreč za ubežanje ne uporablja Konteksti se lahko tudi plastijo, do česar pride, ko vstavimo JavaScript ali CSS v HTML. To je mogoče storiti na dva različna načina, z elementom in atributom: -```html +```latte @@ -132,7 +132,7 @@ Imejmo niz `Rock'n'Roll`. Če ga boste izpisovali v besedilu HTML, ravno v tem primeru ni treba delati nobenih zamenjav, ker niz ne vsebuje nobenega znaka s posebnim pomenom. Druga situacija nastane, če ga izpišete znotraj atributa HTML, omejenega z enojnimi narekovaji. V takem primeru je treba narekovaje ubežati v HTML entitete: -```html +```latte
``` @@ -152,13 +152,13 @@ alert('Rock\'n\'Roll'); Če to kodo vstavimo v dokument HTML s pomočjo ` ``` Če pa bi jo želeli vstaviti v atribut HTML, moramo še ubežati narekovaje v HTML entitete: -```html +```latte
``` @@ -170,7 +170,7 @@ https://example.org/?a=Jazz&b=Rock%27n%27Roll In ko ta niz izpišemo v atributu, še uporabimo ubežanje po tem kontekstu in zamenjamo `&` za `&`: -```html +```latte ``` @@ -314,7 +314,7 @@ Opazite, da okoli vrednosti atributov ni narekovajev. Koder jih je lahko pozabil Napadalec kot opis slike vstavi spretno sestavljen niz `foo onload=alert('Hacked!')`. Že vemo, da Twig ne more prepoznati, ali se spremenljivka izpisuje v toku besedila HTML, znotraj atributa, komentarja HTML, itd., skratka ne razlikuje kontekstov. In samo mehansko pretvarja znake `< > & ' "` v HTML entitete. Torej bo rezultatna koda izgledala takole: -```html +```latte foo ``` @@ -330,7 +330,7 @@ Zdaj si poglejmo, kako se z enako predlogo spopade Latte: Latte vidi predlogo enako kot vi. Za razliko od Twiga razume HTML in ve, da se spremenljivka izpisuje kot vrednost atributa, ki ni v narekovajih. Zato jih dopolni. Ko napadalec vstavi enak opis, bo rezultatna koda izgledala takole: -```html +```latte foo onload=alert('Hacked!') ``` diff --git a/latte/tr/custom-tags.texy b/latte/tr/custom-tags.texy index 402e361e28..8f582915f0 100644 --- a/latte/tr/custom-tags.texy +++ b/latte/tr/custom-tags.texy @@ -923,7 +923,7 @@ class MyLatteExtension extends Extension Oluşturulan HTML: -```html +```latte Sil ``` diff --git a/latte/tr/safety-first.texy b/latte/tr/safety-first.texy index a4943315fd..9e550024e2 100644 --- a/latte/tr/safety-first.texy +++ b/latte/tr/safety-first.texy @@ -33,7 +33,7 @@ echo '

' . $search . ' için arama sonuçları

'; Saldırgan, arama kutusuna ve dolayısıyla `$search` değişkenine `` gibi HTML kodu da dahil olmak üzere herhangi bir karakter dizisi yazabilir. Çıktı hiçbir şekilde işlenmediği için, görüntülenen sayfanın bir parçası haline gelir: -```html +```latte

için arama sonuçları

``` @@ -59,7 +59,7 @@ echo '' . $imageAlt . ''; Saldırganın açıklama olarak akıllıca oluşturulmuş bir karakter dizisi `" onload="alert('Hacklendiniz!')` eklemesi yeterlidir ve yazdırma işlenmezse, sonuç kodu şöyle görünecektir: -```html +```latte ``` @@ -91,7 +91,7 @@ Bağlama Duyarlı Kaçış Bağlam kelimesiyle tam olarak ne kastediliyor? Belgenin içinde, yazdırılan verilerin işlenmesi için kendi kuralları olan bir yerdir. Belgenin türüne (HTML, XML, CSS, JavaScript, düz metin, ...) bağlıdır ve belirli bölümlerinde farklılık gösterebilir. Örneğin, bir HTML belgesinde, çok farklı kuralların geçerli olduğu birçok yer (bağlam) vardır. Kaç tane olduğuna şaşırabilirsiniz. İşte ilk dördü: -```html +```latte

#metin

@@ -108,7 +108,7 @@ HTML yorumlarının içi ilginçtir. Burada kaçış için HTML varlıkları kul Bağlamlar ayrıca katmanlanabilir, bu da HTML'e JavaScript veya CSS eklediğimizde olur. Bu iki farklı şekilde yapılabilir, öğe ve nitelik ile: -```html +```latte @@ -132,7 +132,7 @@ Bir Örnek İster misiniz? Bunu HTML metninde yazdırırsanız, bu özel durumda herhangi bir değiştirme yapmaya gerek yoktur, çünkü dize özel anlamı olan hiçbir karakter içermez. Tek tırnak içine alınmış bir HTML niteliği içinde yazdırırsanız durum farklıdır. Bu durumda, tırnak işaretlerini HTML varlıklarına kaçış işlemine tabi tutmak gerekir: -```html +```latte
``` @@ -152,13 +152,13 @@ alert('Rock\'n\'Roll'); Bu kodu ` ``` Ancak bunu bir HTML niteliğine eklemek istersek, tırnak işaretlerini de HTML varlıklarına kaçış işlemine tabi tutmamız gerekir: -```html +```latte
``` @@ -170,7 +170,7 @@ https://example.org/?a=Jazz&b=Rock%27n%27Roll Ve bu dizeyi bir nitelikte yazdırdığımızda, bu bağlama göre kaçış işlemini de uygular ve `&` yerine `&` yazarız: -```html +```latte ``` @@ -314,7 +314,7 @@ Nitelik değerlerinin etrafında tırnak işareti olmadığına dikkat edin. Kod Saldırgan, resim açıklaması olarak akıllıca oluşturulmuş bir karakter dizisi `foo onload=alert('Hacklendiniz!')` ekler. Twig'in değişkenin HTML metin akışında mı, bir nitelik içinde mi, HTML yorumunda mı vb. yazdırıldığını anlayamayacağını, kısacası bağlamları ayırt etmediğini zaten biliyoruz. Ve yalnızca `< > & ' "` karakterlerini mekanik olarak HTML varlıklarına dönüştürür. Yani sonuç kodu şöyle görünecektir: -```html +```latte foo ``` @@ -330,7 +330,7 @@ Sahte `onload` niteliği sayfanın bir parçası haline geldi ve tarayıcı resm Latte şablonu sizin gördüğünüz gibi görür. Twig'in aksine, HTML'i anlar ve değişkenin tırnak içinde olmayan bir niteliğin değeri olarak yazdırıldığını bilir. Bu yüzden onları ekler. Saldırgan aynı açıklamayı eklediğinde, sonuç kodu şöyle görünecektir: -```html +```latte foo onload=alert('Hacklendiniz!') ``` diff --git a/latte/uk/custom-tags.texy b/latte/uk/custom-tags.texy index 96af747f17..c2e63f4ab1 100644 --- a/latte/uk/custom-tags.texy +++ b/latte/uk/custom-tags.texy @@ -923,7 +923,7 @@ class MyLatteExtension extends Extension Згенерований HTML: -```html +```latte Видалити ``` diff --git a/latte/uk/safety-first.texy b/latte/uk/safety-first.texy index 60ff086270..b5fd634f04 100644 --- a/latte/uk/safety-first.texy +++ b/latte/uk/safety-first.texy @@ -33,7 +33,7 @@ echo '

Результати пошуку для ' . $search . '

'; Зловмисник може в поле пошуку і, відповідно, в змінну `$search` записати будь-який рядок, тобто і HTML-код, як ``. Оскільки вивід ніяк не обробляється, він стане частиною відображеної сторінки: -```html +```latte

Результати пошуку для

``` @@ -59,7 +59,7 @@ echo '' . $imageAlt . ''; Зловмиснику достатньо як опис вставити хитро складений рядок `" onload="alert('Hacked!')`, і якщо виведення не буде оброблено, кінцевий код виглядатиме так: -```html +```latte ``` @@ -91,7 +91,7 @@ echo '' . $imageAlt . ''; Що саме мається на увазі під словом контекст? Це місце в документі з власними правилами обробки виведених даних. Воно залежить від типу документа (HTML, XML, CSS, JavaScript, plain text, ...) і може відрізнятися в його конкретних частинах. Наприклад, в HTML-документі є ціла низка таких місць (контекстів), де діють дуже різні правила. Можливо, ви будете здивовані, скільки їх є. Ось перша четвірка: -```html +```latte

#text

@@ -108,7 +108,7 @@ echo '' . $imageAlt . ''; Контексти також можуть нашаровуватися, що відбувається, коли ми вставляємо JavaScript або CSS в HTML. Це можна зробити двома різними способами: елементом та атрибутом: -```html +```latte @@ -132,7 +132,7 @@ echo '' . $imageAlt . ''; Якщо ви будете виводити його в HTML-тексті, саме в цьому випадку не потрібно робити жодних замін, оскільки рядок не містить жодного символу зі спеціальним значенням. Інша ситуація виникне, якщо ви виведете його всередині HTML-атрибута, взятого в одинарні лапки. У такому випадку потрібно екранувати лапки на HTML-сутності: -```html +```latte
``` @@ -152,13 +152,13 @@ alert('Rock\'n\'Roll'); Якщо цей код вставити в HTML-документ за допомогою ` ``` Однак, якби ми хотіли вставити його в HTML-атрибут, ми повинні ще екранувати лапки на HTML-сутності: -```html +```latte
``` @@ -170,7 +170,7 @@ https://example.org/?a=Jazz&b=Rock%27n%27Roll І коли ми виводимо цей рядок в атрибуті, ще застосовуємо екранування відповідно до цього контексту і замінюємо `&` на `&`: -```html +```latte ``` @@ -314,7 +314,7 @@ Latte бачить шаблон так само, як і ви. Розуміє HT Зловмисник як опис зображення вставляє хитро складений рядок `foo onload=alert('Hacked!')`. Ми вже знаємо, що Twig не може розпізнати, чи виводиться змінна в потоці HTML-тексту, всередині атрибута, HTML-коментаря тощо, коротше кажучи, не розрізняє контексти. І лише механічно перетворює символи `< > & ' "` на HTML-сутності. Отже, кінцевий код виглядатиме так: -```html +```latte foo ``` @@ -330,7 +330,7 @@ Latte бачить шаблон так само, як і ви. Розуміє HT Latte бачить шаблон так само, як і ви. На відміну від Twig, він розуміє HTML і знає, що змінна виводиться як значення атрибута, який не взятий у лапки. Тому він їх доповнить. Коли зловмисник вставить той самий опис, кінцевий код виглядатиме так: -```html +```latte foo onload=alert('Hacked!') ``` diff --git a/mail/cs/@home.texy b/mail/cs/@home.texy index c2db2cff9a..3dde556165 100644 --- a/mail/cs/@home.texy +++ b/mail/cs/@home.texy @@ -176,6 +176,78 @@ V šabloně potom vytváříme odkazy tak, jak jsme zvyklí. Všechny odkazy vyt ``` +Inlinování CSS +============== + +[api:Nette\Mail\CssInliner] převádí CSS pravidla na inline atributy `style`, aby se e-maily zobrazovaly správně ve všech klientech. Zároveň generuje HTML atributy pro kompatibilitu s Outlookem. + +.[note] +Vyžaduje PHP 8.4 nebo novější a rozšíření `dom`. + +Většina e-mailových klientů má omezenou podporu značky ` +

Hello world

+``` + +Výsledek po inlinování bude (značka ` +

Hello world

+``` + +The result after inlining will be (the `