Skip to content

Commit 0e81466

Browse files
committed
Add boolean attribute support to WP_HTML_Template
Implements ticket #60229 requirement for boolean attributes: - `true` converts `disabled="</%d>"` to `disabled` (boolean form) - `false` or `null` removes the attribute entirely - Only works when placeholder is the entire attribute value - Partial placeholders with boolean values return false
1 parent 9c3ee7d commit 0e81466

2 files changed

Lines changed: 161 additions & 5 deletions

File tree

src/wp-includes/html-api/class-wp-html-template.php

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -203,12 +203,22 @@ public function get_tag_attributes(): array {
203203
}
204204
}
205205

206-
// New: append placeholder edit and register name.
206+
// Detect if placeholder is the entire attribute value.
207+
$is_whole_attribute = (
208+
$match_start === $attribute->value_starts_at &&
209+
$match_start + $match_length === $end
210+
);
211+
212+
// Append placeholder edit with attribute metadata.
207213
$this->edits[] = array(
208-
'start' => $match_start,
209-
'length' => $match_length,
210-
'placeholder' => $placeholder,
211-
'context' => 'attribute',
214+
'start' => $match_start,
215+
'length' => $match_length,
216+
'placeholder' => $placeholder,
217+
'context' => 'attribute',
218+
'is_whole_attribute' => $is_whole_attribute,
219+
'attr_name' => $is_whole_attribute ? $attribute->name : null,
220+
'attr_start' => $is_whole_attribute ? $attribute->start : null,
221+
'attr_length' => $is_whole_attribute ? $attribute->length : null,
212222
);
213223
$this->placeholder_names[ $placeholder ] = true;
214224

@@ -403,6 +413,22 @@ public function push_update( WP_HTML_Text_Replacement $update ) {
403413
$processor->push_update(
404414
new WP_HTML_Text_Replacement( $edit['start'], $edit['length'], $escaped ),
405415
);
416+
} elseif ( true === $value ) {
417+
// Boolean true: convert to boolean attribute (remove value, keep name).
418+
if ( empty( $edit['is_whole_attribute'] ) ) {
419+
return false;
420+
}
421+
$processor->push_update(
422+
new WP_HTML_Text_Replacement( $edit['attr_start'], $edit['attr_length'], $edit['attr_name'] ),
423+
);
424+
} elseif ( false === $value || null === $value ) {
425+
// Boolean false or null: remove entire attribute.
426+
if ( empty( $edit['is_whole_attribute'] ) ) {
427+
return false;
428+
}
429+
$processor->push_update(
430+
new WP_HTML_Text_Replacement( $edit['attr_start'], $edit['attr_length'], '' ),
431+
);
406432
} else {
407433
// @todo doing it wrong.
408434
return false;

tests/phpunit/tests/html-api/wpHtmlTemplate.php

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,136 @@ public function test_warns_on_omit_replacement() {
939939
$template->bind( array() );
940940
}
941941

942+
/**
943+
* Verifies that boolean true creates a boolean attribute.
944+
*
945+
* @ticket 60229
946+
*
947+
* @covers ::from
948+
* @covers ::bind
949+
* @covers ::render
950+
*/
951+
public function test_boolean_true_creates_boolean_attribute() {
952+
$result = T::from( '<input disabled="</%disabled>">' )
953+
->bind( array( 'disabled' => true ) )
954+
->render();
955+
$this->assertEqualHTML( '<input disabled>', $result );
956+
}
957+
958+
/**
959+
* Verifies that boolean false removes the attribute.
960+
*
961+
* @ticket 60229
962+
*
963+
* @covers ::from
964+
* @covers ::bind
965+
* @covers ::render
966+
*/
967+
public function test_boolean_false_removes_attribute() {
968+
$result = T::from( '<input disabled="</%disabled>" type="text">' )
969+
->bind( array( 'disabled' => false ) )
970+
->render();
971+
$this->assertEqualHTML( '<input type="text">', $result );
972+
}
973+
974+
/**
975+
* Verifies that null removes the attribute (same as false).
976+
*
977+
* @ticket 60229
978+
*
979+
* @covers ::from
980+
* @covers ::bind
981+
* @covers ::render
982+
*/
983+
public function test_null_removes_attribute() {
984+
$result = T::from( '<input class="</%class>" type="text">' )
985+
->bind( array( 'class' => null ) )
986+
->render();
987+
$this->assertEqualHTML( '<input type="text">', $result );
988+
}
989+
990+
/**
991+
* Verifies that boolean with partial placeholder returns false.
992+
*
993+
* @ticket 60229
994+
*
995+
* @covers ::from
996+
* @covers ::bind
997+
* @covers ::render
998+
*/
999+
public function test_partial_placeholder_rejects_boolean() {
1000+
$result = T::from( '<input class="prefix-</%suffix>">' )
1001+
->bind( array( 'suffix' => true ) )
1002+
->render();
1003+
$this->assertFalse( $result );
1004+
}
1005+
1006+
/**
1007+
* @ticket 60229
1008+
*
1009+
* @dataProvider data_boolean_attribute_handling
1010+
*
1011+
* @covers ::from
1012+
* @covers ::bind
1013+
* @covers ::render
1014+
*/
1015+
public function test_boolean_attribute_handling( string $template, array $replacements, string $expected ) {
1016+
$result = T::from( $template )->bind( $replacements )->render();
1017+
$this->assertEqualHTML( $expected, $result );
1018+
}
1019+
1020+
public static function data_boolean_attribute_handling() {
1021+
return array(
1022+
'true creates boolean attribute' => array(
1023+
'<input disabled="</%disabled>">',
1024+
array( 'disabled' => true ),
1025+
'<input disabled>',
1026+
),
1027+
1028+
'false removes attribute' => array(
1029+
'<input disabled="</%disabled>" type="text">',
1030+
array( 'disabled' => false ),
1031+
'<input type="text">',
1032+
),
1033+
1034+
'null removes attribute' => array(
1035+
'<input class="</%class>" type="text">',
1036+
array( 'class' => null ),
1037+
'<input type="text">',
1038+
),
1039+
1040+
'empty string keeps attribute with empty value' => array(
1041+
'<input value="</%value>">',
1042+
array( 'value' => '' ),
1043+
'<input value="">',
1044+
),
1045+
1046+
'mixed boolean and string replacements' => array(
1047+
'<input disabled="</%d>" value="</%v>">',
1048+
array(
1049+
'd' => true,
1050+
'v' => 'test',
1051+
),
1052+
'<input disabled value="test">',
1053+
),
1054+
1055+
'multiple attributes, one removed' => array(
1056+
'<input class="</%c>" id="</%i>" name="field">',
1057+
array(
1058+
'c' => false,
1059+
'i' => 'my-id',
1060+
),
1061+
'<input id="my-id" name="field">',
1062+
),
1063+
1064+
'single-quoted attribute with boolean' => array(
1065+
"<input disabled='</%disabled>'>",
1066+
array( 'disabled' => true ),
1067+
'<input disabled>',
1068+
),
1069+
);
1070+
}
1071+
9421072
/**
9431073
* Verifies render() returns false for integer replacement value.
9441074
*

0 commit comments

Comments
 (0)