diff --git a/docs/en/appendices/6-0-migration-guide.md b/docs/en/appendices/6-0-migration-guide.md
index 482b2039a8..5b62be5c74 100644
--- a/docs/en/appendices/6-0-migration-guide.md
+++ b/docs/en/appendices/6-0-migration-guide.md
@@ -295,3 +295,12 @@ still uses other Diactoros classes or functions, declare
classes and actions using PHP attributes such as `#[Get]`, `#[Post]`, `#[Scope]`,
`#[Resource]`, and more. See [Attribute Routing](../development/attribute-routing)
for full documentation.
+
+### View
+
+- `HtmlString` and the `Cake\View\html()` function wrap trusted markup, while
+ `HtmlString::render()` escapes ordinary strings and preserves values
+ implementing the new `HtmlStringable` contract. Supported HtmlHelper and
+ FormHelper content arguments accept these values without disabling escaping.
+ See [Rendering Trusted HTML](../views#rendering-trusted-html) for usage and
+ escaping rules.
diff --git a/docs/en/core-libraries/global-constants-and-functions.md b/docs/en/core-libraries/global-constants-and-functions.md
index 61b6f930e9..cefd3cba98 100644
--- a/docs/en/core-libraries/global-constants-and-functions.md
+++ b/docs/en/core-libraries/global-constants-and-functions.md
@@ -183,6 +183,10 @@ distribute the code), since it's a full emulation wrapper.
Convenience wrapper for `htmlspecialchars()`.
+This function also escapes `Cake\View\HtmlStringable` values. To escape
+ordinary strings while preserving trusted HTML, use
+[`HtmlString::render()`](../views#rendering-trusted-html).
+
### pluginSplit()
`function` **pluginSplit(string $name, boolean $dotAppend = false, string $plugin = null)**
@@ -200,6 +204,29 @@ Split the namespace from the classname.
Commonly used like `list($namespace, $className) = namespaceSplit('Cake\Core\App');`
+## View Functions
+
+### html()
+
+`function` Cake\\View\\**html(string $html): HtmlString**
+
+Wraps trusted markup in an immutable `Cake\View\HtmlString` value. Import
+the namespaced function before using it:
+
+```php
+use function Cake\View\html;
+
+echo $this->Html->link(html('Read more'), '/articles');
+```
+
+This function marks markup as trusted and does not sanitize it. See
+[Rendering Trusted HTML](../views#rendering-trusted-html) for escaping rules,
+helper support, and custom implementations.
+
+::: info Added in version 6.0.0
+The `Cake\View\html()` function was added.
+:::
+
## Core Definition Constants
Most of the following constants refer to paths in your application.
diff --git a/docs/en/views.md b/docs/en/views.md
index 7b82ce69a2..099b4be4b2 100644
--- a/docs/en/views.md
+++ b/docs/en/views.md
@@ -157,6 +157,105 @@ the `h()` function:
= h($user->bio); ?>
```
+### Rendering Trusted HTML
+
+When a value can contain either plain text or HTML intended to render directly,
+use `Cake\View\HtmlString` to distinguish trusted markup from text that needs
+escaping. `HtmlString` is an immutable value object:
+
+```php
+use Cake\View\HtmlString;
+use function Cake\View\html;
+
+$content = new HtmlString('Featured article');
+
+// The html() convenience function creates the same value object.
+$content = html('Featured article');
+
+echo $content;
+// Featured article
+```
+
+`HtmlString` marks markup as trusted; it does not sanitize it. Only wrap HTML
+you control, and escape any untrusted values before including them:
+
+```php
+$content = html('' . h($article->title) . '');
+```
+
+Use `HtmlString::render()` in templates or custom renderers that accept either
+a string or an `HtmlStringable` value. It escapes ordinary strings with `h()`
+and renders trusted HTML unchanged:
+
+```php
+echo HtmlString::render('News & updates');
+// News & updates
+
+echo HtmlString::render(html('News'));
+// News
+```
+
+An explicit call to `h()` still escapes trusted markup:
+
+```php
+echo h(html('News'));
+// <strong>News</strong>
+```
+
+Casting an `HtmlString` to a string returns its original markup. It also
+implements `JsonSerializable`, so `json_encode()` serializes it as a JSON string.
+
+#### Using Trusted HTML with Helpers
+
+The [HtmlHelper](views/helpers/html#creating-links) and
+[FormHelper](views/helpers/form#rendering-trusted-html) accept trusted HTML in
+supported content arguments without requiring `'escape' => false`:
+
+```php
+echo $this->Html->link(html('Read more'), '/articles');
+// Read more
+
+echo $this->Form->button(html('Save'));
+//
+```
+
+Helper methods still return strings. To pass their output as trusted content
+to another helper, wrap it with `html()`. Attribute values continue to be
+escaped according to the helper's attribute escaping options.
+
+#### Implementing HtmlStringable
+
+Custom components and plugins can implement `Cake\View\HtmlStringable`
+instead of returning an `HtmlString`. The interface extends `Stringable` and
+requires a `__toString(): string` method that returns trusted HTML:
+
+```php
+use Cake\View\HtmlStringable;
+
+final class Badge implements HtmlStringable
+{
+ public function __construct(private string $label)
+ {
+ }
+
+ public function __toString(): string
+ {
+ return '' . h($this->label) . '';
+ }
+}
+
+echo HtmlString::render(new Badge('News & updates'));
+// News & updates
+```
+
+Implementations are responsible for escaping any untrusted values in their
+markup. The contract applies to HTML content, and does not bypass attribute
+escaping or explicit calls to `h()`.
+
+::: info Added in version 6.0.0
+`HtmlString`, `HtmlStringable`, and `Cake\View\html()` were added.
+:::
+
### Setting View Variables
`method` Cake\\View\\View::**set**(array|string $name, mixed $value = null): static
diff --git a/docs/en/views/helpers/form.md b/docs/en/views/helpers/form.md
index 43334f1b94..8d7132a0f4 100644
--- a/docs/en/views/helpers/form.md
+++ b/docs/en/views/helpers/form.md
@@ -13,6 +13,54 @@ re-population and layout. The FormHelper is also flexible - it will do almost
everything for you using conventions, or you can use specific methods to get
only what you need.
+## Rendering Trusted HTML
+
+FormHelper accepts [HtmlString](../../views#rendering-trusted-html) and other
+`Cake\View\HtmlStringable` values in the following content positions:
+
+- Captions for `button()` and `postButton()`, and content for `postLink()` and
+ `deleteLink()`.
+- Label text for `label()`, the `label` option of `control()`, and the `text`
+ key of a label options array.
+- Option labels for `radio()` and `multiCheckbox()`.
+- Explicit error messages passed to `error()` or the `error` option of
+ `control()`, including individual messages in an array of overrides.
+
+These values render without escaping their markup, while ordinary strings
+continue to follow the existing escaping options:
+
+```php
+use function Cake\View\html;
+
+echo $this->Form->button(html('Save'));
+
+echo $this->Form->control('name', [
+ 'label' => html('Your name'),
+]);
+
+echo $this->Form->radio('priority', [
+ ['value' => 'high', 'text' => html('High')],
+ ['value' => 'normal', 'text' => 'Normal'],
+]);
+
+// Override one message while retaining other validation messages.
+echo $this->Form->error('name', [
+ '_empty' => html('Please enter your name.'),
+]);
+```
+
+Only explicit error overrides are treated as trusted HTML. Messages obtained
+from the form context, including fallback messages without an override, remain
+escaped by default even if they implement `HtmlStringable`.
+
+Wrapping a value does not bypass escaping in attributes, input values,
+textarea contents, or select option text. Only wrap markup you control;
+`HtmlString` does not sanitize HTML.
+
+::: info Added in version 6.0.0
+Support for `HtmlStringable` content was added.
+:::
+
## Starting a Form
`method` Cake\\View\\Helper\\FormHelper::**create**(mixed $context = null, array $options = []): string
@@ -1833,10 +1881,10 @@ If it was 2009, you would get the following:
## Creating Labels
-`method` Cake\\View\\Helper\\FormHelper::**label**(string $fieldName, string $text, array $options): string
+`method` Cake\\View\\Helper\\FormHelper::**label**(string $fieldName, HtmlStringable|string|null $text = null, array $options = []): string
- `$fieldName` - A field name in the form `'Modelname.fieldname'`.
-- `$text` - An optional string providing the label caption text.
+- `$text` - An optional string or `HtmlStringable` value providing the label caption.
- `$options` - Optional. Array containing any of the
[General Control Options](#general-control-options) as well as any valid HTML attributes.
@@ -1882,7 +1930,7 @@ field errors and when necessary display customized error messages.
`method` Cake\\View\\Helper\\FormHelper::**error**(string $fieldName, mixed $text, array $options): string
- `$fieldName` - A field name in the form `'Modelname.fieldname'`.
-- `$text` - Optional. A string or array providing the error message(s). If an
+- `$text` - Optional. A string, `HtmlStringable` value, or array providing the error message(s). If an
array, then it should be a hash of key names => messages. Defaults to
`null`.
- `$options` - An optional array that can only contain a boolean with the key
@@ -2053,15 +2101,15 @@ more complex button content you should use `button()`.
### Creating Button Elements
-`method` Cake\\View\\Helper\\FormHelper::**button**(string $title, array $options = []): string
+`method` Cake\\View\\Helper\\FormHelper::**button**(HtmlStringable|string $content, array $options = []): string
-- `$title` - Mandatory string providing the button's text caption.
+- `$content` - Mandatory string or `HtmlStringable` value providing the button's caption.
- `$options` - An optional array including any of the
[General Control Options](#general-control-options), or of the specific button options (see below)
as well as any valid HTML attributes.
-Creates an HTML button with the specified title and a default type
-of `'button'`.
+Creates an HTML button with the specified content and a default type
+of `'submit'`.
**Options for Button**
@@ -2072,9 +2120,9 @@ of `'button'`.
as `submit()` does. This is the default type.
2. `'reset'` - Creates a form reset button.
3. `'button'` - Creates a standard push button.
-- `escapeTitle` - Boolean. If set to `true` it will HTML encode
- the value provided inside `$title`. Defaults to `true`.
- `escape` - Boolean. If set to `true` it will HTML encode
+ ordinary strings provided inside `$content`. Defaults to `true`.
+- `escapeAttributes` - Boolean. If set to `true` it will HTML encode
all the HTML attributes generated for the button. Defaults to `true`.
- `confirm` - The confirmation message to display on click. Defaults to
`null`.
@@ -2097,13 +2145,13 @@ Will output:
```
-Example use of the `'escapeTitle'` option:
+Example use of the `'escape'` option:
```php
// Will render unescaped HTML.
echo $this->Form->button('Submit Form', [
'type' => 'submit',
- 'escapeTitle' => false,
+ 'escape' => false,
]);
```
@@ -2165,10 +2213,10 @@ template option on `FormHelper`.
### Creating POST Buttons
-`method` Cake\\View\\Helper\\FormHelper::**postButton**(string $title, mixed $url, array $options = []): string
+`method` Cake\\View\\Helper\\FormHelper::**postButton**(HtmlStringable|string $content, array|string $url, array $options = []): string
-- `$title` - Mandatory string providing the button's text caption. By default
- not HTML encoded.
+- `$content` - Mandatory string or `HtmlStringable` value providing the button's
+ caption. Ordinary strings are HTML encoded by default.
- `$url` - The URL of the form provided as a string or as array.
- `$options` - An optional array including any of the
[General Control Options](#general-control-options), or of the specific options (see below) as well
@@ -2218,9 +2266,9 @@ inside opened forms.
### Creating POST Links
-`method` Cake\\View\\Helper\\FormHelper::**postLink**(string $title, array|string|null $url = null, array $options = []): string
+`method` Cake\\View\\Helper\\FormHelper::**postLink**(HtmlStringable|string $content, array|string|null $url = null, array $options = []): string
-- `$title` - Mandatory string providing the text to be wrapped in ``
+- `$content` - Mandatory string or `HtmlStringable` value to be wrapped in ``
tags.
- `$url` - Optional. String or array which contains the URL
of the form (Cake-relative or external URL starting with `http://`).
@@ -2269,9 +2317,9 @@ use `Cake\View\Helper\FormHelper::button()` or
### Creating DELETE Links
-`method` Cake\\View\\Helper\\FormHelper::**deleteLink**(string $title, array|string|null $url = null, array $options = []): string
+`method` Cake\\View\\Helper\\FormHelper::**deleteLink**(HtmlStringable|string $content, array|string|null $url = null, array $options = []): string
-- `$title` - Mandatory string providing the text to be wrapped in ``
+- `$content` - Mandatory string or `HtmlStringable` value to be wrapped in ``
tags.
- `$url` - Optional. String or array which contains the URL
of the form (Cake-relative or external URL starting with `http://`).
diff --git a/docs/en/views/helpers/html.md b/docs/en/views/helpers/html.md
index 959620cee1..42e80e0fca 100644
--- a/docs/en/views/helpers/html.md
+++ b/docs/en/views/helpers/html.md
@@ -313,11 +313,11 @@ Will output:
### Creating Links
-`method` Cake\\View\\Helper\\HtmlHelper::**link**(array|string $title, array|string|null $url = null, array $options = []): string
+`method` Cake\\View\\Helper\\HtmlHelper::**link**(HtmlStringable|array|string $content, array|string|null $url = null, array $options = []): string
General purpose method for creating HTML links. Use `$options` to
specify attributes for the element and whether or not the
-`$title` should be escaped.
+`$content` should be escaped.
```php
echo $this->Html->link(
@@ -387,7 +387,7 @@ Will output:
View image
```
-HTML special characters in `$title` will be converted to HTML
+HTML special characters in string `$content` will be converted to HTML
entities. To disable this conversion, set the escape option to
`false` in the `$options` array.
@@ -407,15 +407,14 @@ Will output:
```
-Setting `escape` to `false` will also disable escaping of attributes of the
-link. You can use the option `escapeTitle` to disable just
-escaping of title and not the attributes.
+The `escape` option controls the link content, while `escapeAttributes`
+controls attribute escaping independently. Attributes are escaped by default:
```php
echo $this->Html->link(
$this->Html->image('recipes/6.jpg', ['alt' => 'Brownies']),
'recipes/view/6',
- ['escapeTitle' => false, 'title' => 'hi "howdy"'],
+ ['escape' => false, 'title' => 'hi "howdy"'],
);
```
@@ -427,12 +426,35 @@ Will output:
```
+You can also wrap trusted content in an [HtmlString](../../views#rendering-trusted-html)
+to render it without changing the escaping options:
+
+```php
+use function Cake\View\html;
+
+echo $this->Html->link(
+ html($this->Html->image('recipes/6.jpg', ['alt' => 'Brownies'])),
+ 'recipes/view/6',
+ ['title' => 'hi "howdy"'],
+);
+```
+
+When the content implements `Cake\View\HtmlStringable`, an explicit URL is
+required. Omitting the URL throws an `InvalidArgumentException`.
+`linkFromPath()` also accepts trusted HTML content. The `tag()`, `div()`, and
+`para()` methods accept it as their text argument and preserve its markup even
+when `'escape' => true`. Attribute escaping is unaffected.
+
+::: info Added in version 6.0.0
+Support for `HtmlStringable` content was added.
+:::
+
Also check `Cake\View\Helper\UrlHelper::build()` method
for more examples of different types of URLs.
### Creating Links from Route Paths
-`method` Cake\\View\\Helper\\HtmlHelper::**linkFromPath**(string $title, string $path, array $params = [], array $options = []): string
+`method` Cake\\View\\Helper\\HtmlHelper::**linkFromPath**(HtmlStringable|string $content, string $path, array $params = [], array $options = []): string
If you want to use route path strings, you can do that using this method: