3131 * id?: string|null,
3232 * class?: string[]|string|null,
3333 * style?: array<string, string>|string|null,
34+ * encode?: bool|null,
3435 * }
3536 * @psalm-type ListHtmlOptions = HtmlOptions&array{
3637 * tag?: string,
37- * encode?: bool,
3838 * item?: Closure(string, array-key):string|null,
3939 * separator?: string,
40- * itemOptions: HtmlOptions,
40+ * itemOptions? : HtmlOptions,
4141 * }
4242 * @psalm-type InputHtmlOptions = HtmlOptions&array {
4343 * value?: string|int|float|\Stringable|bool|null,
5252 * }
5353 * @psalm-type SelectHtmlOptions = HtmlOptions&array{
5454 * encodeSpaces?: bool,
55- * encode?: bool,
5655 * prompt?: array{
5756 * text: string,
5857 * options: HtmlOptions,
@@ -277,11 +276,13 @@ public static function escapeJavaScriptStringValue($value): string
277276 *
278277 * @param bool|string|null $name The tag name. If $name is `null` or `false`, the corresponding content will be
279278 * rendered without any tag.
280- * @param string $content The content to be enclosed between the start and end tags. It will not be HTML-encoded.
281- * If this is coming from end users, you should consider {@see encode()} it to prevent XSS attacks .
279+ * @param string $content The content to be enclosed between the start and end tags. It will be HTML-encoded by
280+ * default to prevent XSS attacks. In order to turn it off, set ` encode` option to `false` .
282281 * @param array $options The HTML tag attributes (HTML options) in terms of name-value pairs. These will be
283282 * rendered as the attributes of the resulting tag. The values will be HTML-encoded using
284283 * {@see encodeAttribute()}. If a value is null, the corresponding attribute will not be rendered.
284+ * The `encode` option is specially handled. If it is `false`, content will be rendered as is. Else it will be
285+ * HTML-encoded with {@see encode()}.
285286 *
286287 * For example when using `['class' => 'my-class', 'target' => '_blank', 'value' => null]` it will result in the
287288 * HTML attributes rendered like this: `class="my-class" target="_blank"`.
@@ -299,6 +300,10 @@ public static function escapeJavaScriptStringValue($value): string
299300 */
300301 public static function tag ($ name , string $ content = '' , array $ options = []): string
301302 {
303+ if (ArrayHelper::remove ($ options , 'encode ' , true )) {
304+ $ content = self ::encode ($ content );
305+ }
306+
302307 if ($ name === null || is_bool ($ name )) {
303308 return $ content ;
304309 }
@@ -364,12 +369,16 @@ public static function endTag($name): string
364369 * corresponding attribute will not be rendered. See {@see renderTagAttributes()} for details on how attributes
365370 * are being rendered.
366371 *
372+ * @psalm-param HtmlOptions|array<empty, empty> $options
373+ *
367374 * @throws JsonException
368375 *
369376 * @return string The generated style tag.
370377 */
371378 public static function style (string $ content , array $ options = []): string
372379 {
380+ /** @psalm-var HtmlOptions $options */
381+ $ options ['encode ' ] ??= false ;
373382 return self ::tag ('style ' , $ content , $ options );
374383 }
375384
@@ -382,12 +391,16 @@ public static function style(string $content, array $options = []): string
382391 * the corresponding attribute will not be rendered. See {@see renderTagAttributes()} for details on how attributes
383392 * are being rendered.
384393 *
394+ * @psalm-param HtmlOptions|array<empty, empty> $options
395+ *
385396 * @throws JsonException
386397 *
387398 * @return string The generated script tag.
388399 */
389400 public static function script (string $ content , array $ options = []): string
390401 {
402+ /** @psalm-var HtmlOptions $options */
403+ $ options ['encode ' ] ??= false ;
391404 return self ::tag ('script ' , $ content , $ options );
392405 }
393406
@@ -848,10 +861,12 @@ public static function textarea(string $name, ?string $value = '', array $option
848861 {
849862 $ options ['name ' ] = $ name ;
850863
851- /** @var bool $doubleEncode */
852- $ doubleEncode = ArrayHelper::remove ($ options , 'doubleEncode ' , true );
864+ if (isset ($ options ['doubleEncode ' ]) && $ options ['doubleEncode ' ] === false ) {
865+ $ value = self ::encode ($ value , false );
866+ $ options ['encode ' ] = false ;
867+ }
853868
854- return self ::tag ('textarea ' , self :: encode ( $ value, $ doubleEncode ) , $ options );
869+ return self ::tag ('textarea ' , ( string ) $ value , $ options );
855870 }
856871
857872 /**
@@ -928,7 +943,9 @@ private static function booleanInput(string $type, string $name, bool $checked,
928943 {
929944 $ options ['checked ' ] = $ checked ;
930945 $ value = array_key_exists ('value ' , $ options ) ? $ options ['value ' ] : '1 ' ;
946+ $ encode = (bool ) ArrayHelper::remove ($ options , 'encode ' , true );
931947
948+ /** @var BooleanInputHtmlOptions $options */
932949 if (isset ($ options ['uncheck ' ])) {
933950 // Add a hidden field so that if the checkbox is not selected, it still submits a value.
934951 $ hiddenOptions = [];
@@ -955,6 +972,9 @@ private static function booleanInput(string $type, string $name, bool $checked,
955972 return $ hidden . self ::input ($ type , $ name , $ value , $ options );
956973 }
957974
975+ $ label = $ encode ? self ::encode ($ label ) : $ label ;
976+ $ labelOptions ['encode ' ] = false ;
977+
958978 if ($ wrapInput ) {
959979 $ input = self ::input ($ type , $ name , $ value , $ options );
960980 return $ hidden . self ::label ($ input . ' ' . $ label , null , $ labelOptions );
@@ -1032,6 +1052,8 @@ public static function dropDownList(string $name, $selection = null, array $item
10321052
10331053 $ selectOptions = self ::renderSelectOptionTags ($ selection , $ items , $ options );
10341054
1055+ $ options ['encode ' ] = false ;
1056+
10351057 return self ::tag ('select ' , "\n" . $ selectOptions . "\n" , $ options );
10361058 }
10371059
@@ -1118,6 +1140,8 @@ public static function listBox(string $name, $selection = null, array $items = [
11181140 /** @var SelectHtmlOptions $options */
11191141 $ selectOptions = self ::renderSelectOptionTags ($ selection , $ items , $ options );
11201142
1143+ $ options ['encode ' ] = false ;
1144+
11211145 return $ hidden . self ::tag ('select ' , "\n" . $ selectOptions . "\n" , $ options );
11221146 }
11231147
@@ -1161,7 +1185,6 @@ public static function listBox(string $name, $selection = null, array $items = [
11611185 * @psalm-param InputHtmlOptions&array{
11621186 * item?: Closure(int, string, string, bool, mixed):string|null,
11631187 * itemOptions?: HtmlOptions|null,
1164- * encode?: bool,
11651188 * separator?: string|null,
11661189 * tag?: string|null,
11671190 * unselect?: string|int|float|\Stringable|bool|null,
@@ -1187,9 +1210,6 @@ public static function checkboxList(string $name, $selection = null, array $item
11871210 /** @psalm-var HtmlOptions $itemOptions */
11881211 $ itemOptions = ArrayHelper::remove ($ options , 'itemOptions ' , []);
11891212
1190- /** @var bool $encode */
1191- $ encode = ArrayHelper::remove ($ options , 'encode ' , true );
1192-
11931213 /** @var string $separator */
11941214 $ separator = ArrayHelper::remove ($ options , 'separator ' , "\n" );
11951215
@@ -1209,7 +1229,7 @@ public static function checkboxList(string $name, $selection = null, array $item
12091229 } else {
12101230 $ lines [] = self ::checkbox ($ name , $ checked , array_merge ([
12111231 'value ' => $ value ,
1212- 'label ' => $ encode ? self :: encode ( $ label ) : $ label ,
1232+ 'label ' => $ label ,
12131233 ], $ itemOptions ));
12141234 }
12151235 $ index ++;
@@ -1229,6 +1249,7 @@ public static function checkboxList(string $name, $selection = null, array $item
12291249 $ hidden = '' ;
12301250 }
12311251
1252+ $ options ['encode ' ] = false ;
12321253 return $ hidden . self ::tag ($ tag , implode ($ separator , $ lines ), $ options );
12331254 }
12341255
@@ -1271,7 +1292,6 @@ public static function checkboxList(string $name, $selection = null, array $item
12711292 * @psalm-param InputHtmlOptions&array{
12721293 * item?: Closure(int, string, string, bool, mixed):string|null,
12731294 * itemOptions?: HtmlOptions|null,
1274- * encode?: bool,
12751295 * separator?: string|null,
12761296 * tag?: string|null,
12771297 * unselect?: string|int|float|\Stringable|bool|null,
@@ -1295,9 +1315,6 @@ public static function radioList(string $name, $selection = null, array $items =
12951315 /** @psalm-var HtmlOptions $itemOptions */
12961316 $ itemOptions = ArrayHelper::remove ($ options , 'itemOptions ' , []);
12971317
1298- /** @var bool $encode */
1299- $ encode = ArrayHelper::remove ($ options , 'encode ' , true );
1300-
13011318 /** @var string $separator */
13021319 $ separator = ArrayHelper::remove ($ options , 'separator ' , "\n" );
13031320
@@ -1329,13 +1346,14 @@ public static function radioList(string $name, $selection = null, array $items =
13291346 } else {
13301347 $ lines [] = self ::radio ($ name , $ checked , array_merge ([
13311348 'value ' => $ value ,
1332- 'label ' => $ encode ? self :: encode ( $ label ) : $ label ,
1349+ 'label ' => $ label ,
13331350 ], $ itemOptions ));
13341351 }
13351352 $ index ++;
13361353 }
13371354 $ visibleContent = implode ($ separator , $ lines );
13381355
1356+ $ options ['encode ' ] = false ;
13391357 return $ hidden . self ::tag ($ tag , $ visibleContent , $ options );
13401358 }
13411359
@@ -1415,7 +1433,7 @@ public static function p(string $content = '', array $options = []): string
14151433 *
14161434 * See {@see renderTagAttributes()} for details on how attributes are being rendered.
14171435 *
1418- * @psalm-param array<array-key, string > $items
1436+ * @psalm-param array<array-key, mixed > $items
14191437 * @psalm-param ListHtmlOptions $options
14201438 *
14211439 * @throws JsonException
@@ -1427,9 +1445,6 @@ public static function ul($items, array $options = []): string
14271445 /** @var string $tag */
14281446 $ tag = ArrayHelper::remove ($ options , 'tag ' , 'ul ' );
14291447
1430- /** @var bool $encode */
1431- $ encode = ArrayHelper::remove ($ options , 'encode ' , true );
1432-
14331448 /** @var Closure(string, array-key):string|null $formatter */
14341449 $ formatter = ArrayHelper::remove ($ options , 'item ' );
14351450
@@ -1445,14 +1460,19 @@ public static function ul($items, array $options = []): string
14451460 }
14461461
14471462 $ results = [];
1463+ /** @var mixed $item */
14481464 foreach ($ items as $ index => $ item ) {
1465+ $ item = (string )$ item ;
14491466 if ($ formatter !== null ) {
14501467 $ results [] = $ formatter ($ item , $ index );
14511468 } else {
1452- $ results [] = self ::tag ('li ' , $ encode ? self :: encode ( $ item ) : $ item , $ itemOptions );
1469+ $ results [] = self ::tag ('li ' , $ item , $ itemOptions );
14531470 }
14541471 }
14551472
1473+ $ separator = self ::encode ($ separator );
1474+ $ options ['encode ' ] = false ;
1475+
14561476 return self ::tag (
14571477 $ tag ,
14581478 $ separator . implode ($ separator , $ results ) . $ separator ,
@@ -1558,7 +1578,7 @@ private static function renderSelectOptionTags($selection, array $items, array &
15581578
15591579 $ lines = [];
15601580 if (isset ($ tagOptions ['prompt ' ])) {
1561- $ promptOptions = ['value ' => '' ];
1581+ $ promptOptions = ['value ' => '' , ' encode ' => false ];
15621582 if (is_string ($ tagOptions ['prompt ' ])) {
15631583 $ promptText = $ tagOptions ['prompt ' ];
15641584 } else {
@@ -1583,6 +1603,7 @@ private static function renderSelectOptionTags($selection, array $items, array &
15831603 foreach ($ items as $ key => $ value ) {
15841604 if (is_array ($ value )) {
15851605 $ groupAttrs = $ groups [$ key ] ?? [];
1606+ $ groupAttrs ['encode ' ] = false ;
15861607 if (!isset ($ groupAttrs ['label ' ])) {
15871608 $ groupAttrs ['label ' ] = $ key ;
15881609 }
@@ -1607,6 +1628,7 @@ private static function renderSelectOptionTags($selection, array $items, array &
16071628 if ($ encodeSpaces ) {
16081629 $ text = str_replace (' ' , ' ' , $ text );
16091630 }
1631+ $ attrs ['encode ' ] = false ;
16101632 $ lines [] = self ::tag ('option ' , $ text , $ attrs );
16111633 }
16121634 }
0 commit comments