From 0571cb32e2d507063caa486356b7ecdb3c260e44 Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
Date: Fri, 13 Sep 2024 21:54:33 +0200
Subject: [PATCH 1/3] Add Dom\Element::$outerHTML getter
Reference: https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#the-outerhtml-property
---
ext/dom/dom_properties.h | 1 +
ext/dom/inner_html_mixin.c | 50 ++++++++++++++++---
ext/dom/php_dom.c | 1 +
ext/dom/php_dom.stub.php | 3 ++
ext/dom/php_dom_arginfo.h | 8 ++-
ext/dom/tests/gh15192.phpt | 6 ++-
.../html/serializer/Element_outerHTML.phpt | 28 +++++++++++
...pt => Element_innerOuterHTML_reading.phpt} | 25 +++++++++-
...lement_innerOuterHTML_reading_errors.phpt} | 22 +++++++-
9 files changed, 131 insertions(+), 13 deletions(-)
create mode 100644 ext/dom/tests/modern/html/serializer/Element_outerHTML.phpt
rename ext/dom/tests/modern/xml/{Element_innerHTML_reading.phpt => Element_innerOuterHTML_reading.phpt} (67%)
rename ext/dom/tests/modern/xml/{Element_innerHTML_reading_errors.phpt => Element_innerOuterHTML_reading_errors.phpt} (77%)
diff --git a/ext/dom/dom_properties.h b/ext/dom/dom_properties.h
index 338b4acc449f..caa8967f384f 100644
--- a/ext/dom/dom_properties.h
+++ b/ext/dom/dom_properties.h
@@ -86,6 +86,7 @@ zend_result dom_element_id_write(dom_object *obj, zval *newval);
zend_result dom_element_schema_type_info_read(dom_object *obj, zval *retval);
zend_result dom_element_inner_html_read(dom_object *obj, zval *retval);
zend_result dom_element_inner_html_write(dom_object *obj, zval *newval);
+zend_result dom_element_outer_html_read(dom_object *obj, zval *retval);
zend_result dom_element_class_list_read(dom_object *obj, zval *retval);
zend_result dom_modern_element_substituted_node_value_read(dom_object *obj, zval *retval);
zend_result dom_modern_element_substituted_node_value_write(dom_object *obj, zval *newval);
diff --git a/ext/dom/inner_html_mixin.c b/ext/dom/inner_html_mixin.c
index 262c85411aaf..b4392ccd80fe 100644
--- a/ext/dom/inner_html_mixin.c
+++ b/ext/dom/inner_html_mixin.c
@@ -55,12 +55,9 @@ static int dom_write_smart_str(void *context, const char *buffer, int len)
return len;
}
-/* https://w3c.github.io/DOM-Parsing/#the-innerhtml-mixin
- * and https://w3c.github.io/DOM-Parsing/#dfn-fragment-serializing-algorithm */
-zend_result dom_element_inner_html_read(dom_object *obj, zval *retval)
+/* https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#fragment-serializing-algorithm-steps */
+static zend_string *dom_element_html_fragment_serialize(dom_object *obj, xmlNodePtr node)
{
- DOM_PROP_NODE(xmlNodePtr, node, obj);
-
/* 1. Let context document be the value of node's node document. */
const xmlDoc *context_document = node->doc;
@@ -73,7 +70,7 @@ zend_result dom_element_inner_html_read(dom_object *obj, zval *retval)
ctx.write_string = dom_inner_html_write_string;
ctx.write_string_len = dom_inner_html_write_string_len;
dom_html5_serialize(&ctx, node);
- ZVAL_STR(retval, smart_str_extract(&output));
+ return smart_str_extract(&output);
}
/* 3. Otherwise, context document is an XML document; return an XML serialization of node passing the flag require well-formed. */
else {
@@ -104,11 +101,21 @@ zend_result dom_element_inner_html_read(dom_object *obj, zval *retval)
if (UNEXPECTED(status < 0)) {
smart_str_free_ex(&str, false);
php_dom_throw_error_with_message(SYNTAX_ERR, "The resulting XML serialization is not well-formed", true);
- return FAILURE;
+ return NULL;
}
- ZVAL_STR(retval, smart_str_extract(&str));
+ return smart_str_extract(&str);
}
+}
+/* https://w3c.github.io/DOM-Parsing/#the-innerhtml-mixin */
+zend_result dom_element_inner_html_read(dom_object *obj, zval *retval)
+{
+ DOM_PROP_NODE(xmlNodePtr, node, obj);
+ zend_string *serialization = dom_element_html_fragment_serialize(obj, node);
+ if (serialization == NULL) {
+ return FAILURE;
+ }
+ ZVAL_STR(retval, serialization);
return SUCCESS;
}
@@ -363,4 +370,31 @@ zend_result dom_element_inner_html_write(dom_object *obj, zval *newval)
return php_dom_pre_insert(obj->document, fragment, context_node, NULL) ? SUCCESS : FAILURE;
}
+/* https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#the-outerhtml-property */
+zend_result dom_element_outer_html_read(dom_object *obj, zval *retval)
+{
+ DOM_PROP_NODE(xmlNodePtr, this, obj);
+
+ /* 1. Let element be a fictional node whose only child is this. */
+ xmlNode element;
+ memset(&element, 0, sizeof(element));
+ element.type = XML_DOCUMENT_FRAG_NODE;
+ element.children = element.last = this;
+ element.doc = this->doc;
+
+ xmlNodePtr old_parent = this->parent;
+ this->parent = &element;
+
+ /* 2. Return the result of running fragment serializing algorithm steps with element and true. */
+ zend_string *serialization = dom_element_html_fragment_serialize(obj, &element);
+
+ this->parent = old_parent;
+
+ if (serialization == NULL) {
+ return FAILURE;
+ }
+ ZVAL_STR(retval, serialization);
+ return SUCCESS;
+}
+
#endif
diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c
index 8198afe5b8ba..a52330a24e8c 100644
--- a/ext/dom/php_dom.c
+++ b/ext/dom/php_dom.c
@@ -1111,6 +1111,7 @@ PHP_MINIT_FUNCTION(dom)
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "previousElementSibling", dom_node_previous_element_sibling_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "nextElementSibling", dom_node_next_element_sibling_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "innerHTML", dom_element_inner_html_read, dom_element_inner_html_write);
+ DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "outerHTML", dom_element_outer_html_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "substitutedNodeValue", dom_modern_element_substituted_node_value_read, dom_modern_element_substituted_node_value_write);
zend_hash_merge(&dom_modern_element_prop_handlers, &dom_modern_node_prop_handlers, NULL, false);
DOM_OVERWRITE_PROP_HANDLER(&dom_modern_element_prop_handlers, "textContent", dom_node_text_content_read, dom_node_text_content_write);
diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php
index 0f7f74841042..acd507920784 100644
--- a/ext/dom/php_dom.stub.php
+++ b/ext/dom/php_dom.stub.php
@@ -1688,6 +1688,9 @@ public function matches(string $selectors): bool {}
/** @virtual */
public string $innerHTML;
+ /** @virtual */
+ public string $outerHTML;
+
/** @virtual */
public string $substitutedNodeValue;
diff --git a/ext/dom/php_dom_arginfo.h b/ext/dom/php_dom_arginfo.h
index e1f230ccbcf0..0fbcba0d74e2 100644
--- a/ext/dom/php_dom_arginfo.h
+++ b/ext/dom/php_dom_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
- * Stub hash: b79ad2b70757f7d65a6b4fd907222a4955264bf6 */
+ * Stub hash: bc53676bcd060f8fd26ff6e92da4983e85c0eb83 */
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_dom_import_simplexml, 0, 1, DOMElement, 0)
ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0)
@@ -3078,6 +3078,12 @@ static zend_class_entry *register_class_Dom_Element(zend_class_entry *class_entr
zend_declare_typed_property(class_entry, property_innerHTML_name, &property_innerHTML_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
zend_string_release(property_innerHTML_name);
+ zval property_outerHTML_default_value;
+ ZVAL_UNDEF(&property_outerHTML_default_value);
+ zend_string *property_outerHTML_name = zend_string_init("outerHTML", sizeof("outerHTML") - 1, 1);
+ zend_declare_typed_property(class_entry, property_outerHTML_name, &property_outerHTML_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
+ zend_string_release(property_outerHTML_name);
+
zval property_substitutedNodeValue_default_value;
ZVAL_UNDEF(&property_substitutedNodeValue_default_value);
zend_string *property_substitutedNodeValue_name = zend_string_init("substitutedNodeValue", sizeof("substitutedNodeValue") - 1, 1);
diff --git a/ext/dom/tests/gh15192.phpt b/ext/dom/tests/gh15192.phpt
index 5ab5d858eceb..c7bf0a543bb9 100644
--- a/ext/dom/tests/gh15192.phpt
+++ b/ext/dom/tests/gh15192.phpt
@@ -10,8 +10,8 @@ $element = $dom2->firstChild;
$dom = new DomDocument();
var_dump($element);
?>
---EXPECTF--
-object(Dom\HTMLElement)#3 (29) {
+--EXPECT--
+object(Dom\HTMLElement)#3 (30) {
["namespaceURI"]=>
string(28) "http://www.w3.org/1999/xhtml"
["prefix"]=>
@@ -40,6 +40,8 @@ object(Dom\HTMLElement)#3 (29) {
NULL
["innerHTML"]=>
string(36) "
foo
"
+ ["outerHTML"]=>
+ string(49) "foo
"
["substitutedNodeValue"]=>
string(3) "foo"
["nodeType"]=>
diff --git a/ext/dom/tests/modern/html/serializer/Element_outerHTML.phpt b/ext/dom/tests/modern/html/serializer/Element_outerHTML.phpt
new file mode 100644
index 000000000000..a8e780d421ea
--- /dev/null
+++ b/ext/dom/tests/modern/html/serializer/Element_outerHTML.phpt
@@ -0,0 +1,28 @@
+--TEST--
+Test reading Element::$outerHTML on HTML documents
+--EXTENSIONS--
+dom
+--FILE--
+foo', LIBXML_NOERROR);
+
+$p = $dom->body->firstChild;
+var_dump($p->outerHTML);
+
+$root = $dom->documentElement;
+var_dump($root->outerHTML);
+
+$unattached_element = $dom->createElement('unattached');
+var_dump($unattached_element->outerHTML);
+
+$template = $dom->createElement('template');
+$template->innerHTML = 'foo
';
+var_dump($template->outerHTML);
+
+?>
+--EXPECT--
+string(10) "foo
"
+string(49) "foo
"
+string(25) ""
+string(31) "foo
"
diff --git a/ext/dom/tests/modern/xml/Element_innerHTML_reading.phpt b/ext/dom/tests/modern/xml/Element_innerOuterHTML_reading.phpt
similarity index 67%
rename from ext/dom/tests/modern/xml/Element_innerHTML_reading.phpt
rename to ext/dom/tests/modern/xml/Element_innerOuterHTML_reading.phpt
index b096fc2c6cc8..76f2d6fecd20 100644
--- a/ext/dom/tests/modern/xml/Element_innerHTML_reading.phpt
+++ b/ext/dom/tests/modern/xml/Element_innerOuterHTML_reading.phpt
@@ -1,5 +1,5 @@
--TEST--
-Test reading Element::$innerHTML on XML documents
+Test reading Element::${inner,outer}HTML on XML documents
--EXTENSIONS--
dom
--FILE--
@@ -16,43 +16,56 @@ function createContainer() {
$container = createContainer();
$container->append("Hello, world!");
var_dump($container->innerHTML);
+var_dump($container->outerHTML);
$container = createContainer();
$container->append($dom->createComment("This is -a- comment"));
var_dump($container->innerHTML);
+var_dump($container->outerHTML);
$container = createContainer();
// Note: intentionally typo'd to check whether the string matching against "xml" happens correctly
// i.e. no bugs with prefix-matching only.
$container->append($dom->createProcessingInstruction("xmll", ""));
var_dump($container->innerHTML);
+var_dump($container->outerHTML);
$container = createContainer();
$container->append($dom->createProcessingInstruction("almostmalformed", ">?"));
var_dump($container->innerHTML);
+var_dump($container->outerHTML);
$container = createContainer();
$element = $container->appendChild(createContainer());
$element->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns', 'http://example.com/');
var_dump($container->innerHTML);
+var_dump($container->outerHTML);
$container = createContainer();
$element = $container->appendChild(createContainer());
$element->setAttributeNS('urn:a', 'name', '');
$element->setAttributeNS('urn:b', 'name', '');
var_dump($container->innerHTML);
+var_dump($container->outerHTML);
$dom = DOM\XMLDocument::createFromFile(__DIR__ . '/../../book.xml');
var_dump($dom->documentElement->innerHTML);
+var_dump($dom->documentElement->outerHTML);
?>
--EXPECT--
string(13) "Hello, world!"
+string(36) "Hello, world!"
string(26) ""
+string(49) ""
string(9) ""
+string(32) ""
string(22) "??>"
+string(45) "??>"
string(12) ""
+string(35) ""
string(72) ""
+string(95) ""
string(167) "
The Grapes of Wrath
@@ -63,3 +76,13 @@ string(167) "
John Steinbeck
"
+string(182) "
+
+ The Grapes of Wrath
+ John Steinbeck
+
+
+ The Pearl
+ John Steinbeck
+
+"
diff --git a/ext/dom/tests/modern/xml/Element_innerHTML_reading_errors.phpt b/ext/dom/tests/modern/xml/Element_innerOuterHTML_reading_errors.phpt
similarity index 77%
rename from ext/dom/tests/modern/xml/Element_innerHTML_reading_errors.phpt
rename to ext/dom/tests/modern/xml/Element_innerOuterHTML_reading_errors.phpt
index 04b9971186c4..585c9c5fd875 100644
--- a/ext/dom/tests/modern/xml/Element_innerHTML_reading_errors.phpt
+++ b/ext/dom/tests/modern/xml/Element_innerOuterHTML_reading_errors.phpt
@@ -1,5 +1,5 @@
--TEST--
-Test reading Element::$innerHTML on XML documents - error cases
+Test reading Element::${inner,outer}HTML on XML documents - error cases
--EXTENSIONS--
dom
--FILE--
@@ -19,6 +19,11 @@ function test($container) {
} catch (DOMException $e) {
echo $e->getMessage(), "\n";
}
+ try {
+ var_dump($container->outerHTML);
+ } catch (DOMException $e) {
+ echo $e->getMessage(), "\n";
+ }
}
$container = createContainer();
@@ -106,3 +111,18 @@ The resulting XML serialization is not well-formed
The resulting XML serialization is not well-formed
The resulting XML serialization is not well-formed
The resulting XML serialization is not well-formed
+The resulting XML serialization is not well-formed
+The resulting XML serialization is not well-formed
+The resulting XML serialization is not well-formed
+The resulting XML serialization is not well-formed
+The resulting XML serialization is not well-formed
+The resulting XML serialization is not well-formed
+The resulting XML serialization is not well-formed
+The resulting XML serialization is not well-formed
+The resulting XML serialization is not well-formed
+The resulting XML serialization is not well-formed
+The resulting XML serialization is not well-formed
+The resulting XML serialization is not well-formed
+The resulting XML serialization is not well-formed
+The resulting XML serialization is not well-formed
+The resulting XML serialization is not well-formed
From c3e308ff797bd030f04e9ec0df373c25116711e5 Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
Date: Sat, 14 Sep 2024 17:13:17 +0200
Subject: [PATCH 2/3] Add Dom\Element::$outerHTML setter
Reference: https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#the-outerhtml-property
---
ext/dom/dom_properties.h | 1 +
ext/dom/inner_html_mixin.c | 83 +++++++++++++++++--
ext/dom/php_dom.c | 2 +-
.../modern/html/parser/Element_outerHTML.phpt | 34 ++++++++
.../Element_outerHTML_invalid_tree.phpt | 17 ++++
.../modern/xml/Element_outerHTML_writing.phpt | 35 ++++++++
.../xml/Element_outerHTML_writing_errors.phpt | 25 ++++++
7 files changed, 188 insertions(+), 9 deletions(-)
create mode 100644 ext/dom/tests/modern/html/parser/Element_outerHTML.phpt
create mode 100644 ext/dom/tests/modern/html/serializer/Element_outerHTML_invalid_tree.phpt
create mode 100644 ext/dom/tests/modern/xml/Element_outerHTML_writing.phpt
create mode 100644 ext/dom/tests/modern/xml/Element_outerHTML_writing_errors.phpt
diff --git a/ext/dom/dom_properties.h b/ext/dom/dom_properties.h
index caa8967f384f..d711aa1a2e55 100644
--- a/ext/dom/dom_properties.h
+++ b/ext/dom/dom_properties.h
@@ -87,6 +87,7 @@ zend_result dom_element_schema_type_info_read(dom_object *obj, zval *retval);
zend_result dom_element_inner_html_read(dom_object *obj, zval *retval);
zend_result dom_element_inner_html_write(dom_object *obj, zval *newval);
zend_result dom_element_outer_html_read(dom_object *obj, zval *retval);
+zend_result dom_element_outer_html_write(dom_object *obj, zval *newval);
zend_result dom_element_class_list_read(dom_object *obj, zval *retval);
zend_result dom_modern_element_substituted_node_value_read(dom_object *obj, zval *retval);
zend_result dom_modern_element_substituted_node_value_write(dom_object *obj, zval *newval);
diff --git a/ext/dom/inner_html_mixin.c b/ext/dom/inner_html_mixin.c
index b4392ccd80fe..4655878c533f 100644
--- a/ext/dom/inner_html_mixin.c
+++ b/ext/dom/inner_html_mixin.c
@@ -341,23 +341,31 @@ static xmlNodePtr dom_xml_fragment_parsing_algorithm(dom_object *obj, const xmlN
return NULL;
}
-/* https://w3c.github.io/DOM-Parsing/#the-innerhtml-mixin
- * and https://w3c.github.io/DOM-Parsing/#dfn-fragment-parsing-algorithm */
-zend_result dom_element_inner_html_write(dom_object *obj, zval *newval)
+/* https://w3c.github.io/DOM-Parsing/#dfn-fragment-parsing-algorithm */
+static xmlNodePtr dom_parse_fragment(dom_object *obj, xmlNodePtr context_node, const zend_string *input)
{
- DOM_PROP_NODE(xmlNodePtr, context_node, obj);
-
- xmlNodePtr fragment;
if (context_node->doc->type == XML_DOCUMENT_NODE) {
- fragment = dom_xml_fragment_parsing_algorithm(obj, context_node, Z_STR_P(newval));
+ return dom_xml_fragment_parsing_algorithm(obj, context_node, input);
} else {
- fragment = dom_html_fragment_parsing_algorithm(obj, context_node, Z_STR_P(newval), obj->document->quirks_mode);
+ return dom_html_fragment_parsing_algorithm(obj, context_node, input, obj->document->quirks_mode);
}
+}
+
+/* https://w3c.github.io/DOM-Parsing/#the-innerhtml-mixin */
+zend_result dom_element_inner_html_write(dom_object *obj, zval *newval)
+{
+ /* 1. We don't do injection sinks, skip. */
+
+ /* 2. Let context be this. */
+ DOM_PROP_NODE(xmlNodePtr, context_node, obj);
+ /* 3. Let fragment be the result of invoking the fragment parsing algorithm steps with context and compliantString. */
+ xmlNodePtr fragment = dom_parse_fragment(obj, context_node, Z_STR_P(newval));
if (fragment == NULL) {
return FAILURE;
}
+ /* 4. If context is a template element, then set context to the template element's template contents (a DocumentFragment). */
if (php_dom_ns_is_fast(context_node, php_dom_ns_is_html_magic_token) && xmlStrEqual(context_node->name, BAD_CAST "template")) {
context_node = php_dom_ensure_templated_content(php_dom_get_private_data(obj), context_node);
if (context_node == NULL) {
@@ -366,6 +374,7 @@ zend_result dom_element_inner_html_write(dom_object *obj, zval *newval)
}
}
+ /* 5. Replace all with fragment within context. */
dom_remove_all_children(context_node);
return php_dom_pre_insert(obj->document, fragment, context_node, NULL) ? SUCCESS : FAILURE;
}
@@ -397,4 +406,62 @@ zend_result dom_element_outer_html_read(dom_object *obj, zval *retval)
return SUCCESS;
}
+/* https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#the-outerhtml-property */
+zend_result dom_element_outer_html_write(dom_object *obj, zval *newval)
+{
+ /* 1. We don't do injection sinks, skip. */
+
+ /* 2. Let parent be this's parent. */
+ DOM_PROP_NODE(xmlNodePtr, this, obj);
+ xmlNodePtr parent = this->parent;
+ bool created_parent = false;
+
+ /* 3. If parent is null, return. */
+ if (parent == NULL) {
+ return SUCCESS;
+ }
+
+ /* 4. If parent is a Document, throw. */
+ if (parent->type == XML_DOCUMENT_NODE || parent->type == XML_HTML_DOCUMENT_NODE) {
+ php_dom_throw_error(INVALID_MODIFICATION_ERR, true);
+ return FAILURE;
+ }
+
+ /* 5. If parent is a DocumentFragment, set parent to the result of creating an element given this's node document, body, and the HTML namespace. */
+ if (parent->type == XML_DOCUMENT_FRAG_NODE) {
+ xmlNsPtr html_ns = php_dom_libxml_ns_mapper_ensure_html_ns(php_dom_get_ns_mapper(obj));
+
+ parent = xmlNewDocNode(parent->doc, html_ns, BAD_CAST "body", NULL);
+ created_parent = true;
+ if (UNEXPECTED(parent == NULL)) {
+ php_dom_throw_error(INVALID_STATE_ERR, true);
+ return FAILURE;
+ }
+ }
+
+ /* 6. Let fragment be the result of invoking the fragment parsing algorithm steps given parent and compliantString. */
+ xmlNodePtr fragment = dom_parse_fragment(obj, parent, Z_STR_P(newval));
+ if (fragment == NULL) {
+ if (created_parent) {
+ xmlFreeNode(parent);
+ }
+ return FAILURE;
+ }
+
+ /* 7. Replace this with fragment within this's parent. */
+ if (!php_dom_pre_insert(obj->document, fragment, this->parent, this)) {
+ xmlFreeNode(fragment);
+ if (created_parent) {
+ xmlFreeNode(parent);
+ }
+ return FAILURE;
+ }
+ xmlUnlinkNode(this);
+ if (created_parent) {
+ ZEND_ASSERT(parent->children == NULL);
+ xmlFreeNode(parent);
+ }
+ return SUCCESS;
+}
+
#endif
diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c
index a52330a24e8c..4745fbcdc294 100644
--- a/ext/dom/php_dom.c
+++ b/ext/dom/php_dom.c
@@ -1111,7 +1111,7 @@ PHP_MINIT_FUNCTION(dom)
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "previousElementSibling", dom_node_previous_element_sibling_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "nextElementSibling", dom_node_next_element_sibling_read, NULL);
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "innerHTML", dom_element_inner_html_read, dom_element_inner_html_write);
- DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "outerHTML", dom_element_outer_html_read, NULL);
+ DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "outerHTML", dom_element_outer_html_read, dom_element_outer_html_write);
DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "substitutedNodeValue", dom_modern_element_substituted_node_value_read, dom_modern_element_substituted_node_value_write);
zend_hash_merge(&dom_modern_element_prop_handlers, &dom_modern_node_prop_handlers, NULL, false);
DOM_OVERWRITE_PROP_HANDLER(&dom_modern_element_prop_handlers, "textContent", dom_node_text_content_read, dom_node_text_content_write);
diff --git a/ext/dom/tests/modern/html/parser/Element_outerHTML.phpt b/ext/dom/tests/modern/html/parser/Element_outerHTML.phpt
new file mode 100644
index 000000000000..401e11b16456
--- /dev/null
+++ b/ext/dom/tests/modern/html/parser/Element_outerHTML.phpt
@@ -0,0 +1,34 @@
+--TEST--
+Test writing Element::$outerHTML on HTML documents
+--EXTENSIONS--
+dom
+--FILE--
+foo', LIBXML_NOERROR);
+$p = $dom->body->firstChild;
+$p->outerHTML = ' '; // intentionally unclosed
+echo $dom->saveXML(), "\n";
+echo $dom->saveHtml(), "\n";
+$div = $dom->body->firstChild;
+$div->outerHTML = "invalid\xffutf-8𐍈𐍈𐍈";
+echo $dom->saveXML(), "\n";
+echo $dom->saveHtml(), "\n";
+
+$dom->body->outerHTML = 'foo
';
+var_dump($dom->body->querySelector('p')); // Should be NULL because the template contents do not participate in the DOM tree
+echo $dom->saveXML(), "\n";
+echo $dom->saveHtml(), "\n";
+
+?>
+--EXPECT--
+
+
+
+
+invalid�utf-8𐍈𐍈𐍈
+invalid�utf-8𐍈𐍈𐍈
+NULL
+
+foo
+foo
diff --git a/ext/dom/tests/modern/html/serializer/Element_outerHTML_invalid_tree.phpt b/ext/dom/tests/modern/html/serializer/Element_outerHTML_invalid_tree.phpt
new file mode 100644
index 000000000000..4803e2067639
--- /dev/null
+++ b/ext/dom/tests/modern/html/serializer/Element_outerHTML_invalid_tree.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Test reading Element::$outerHTML on HTML documents - invalid tree variation
+--EXTENSIONS--
+dom
+--CREDITS--
+Dennis Snell
+--FILE--
+Link
', LIBXML_NOERROR);
+$p = $dom->body->querySelector('p');
+$p->outerHTML = 'Another Link';
+echo $dom->saveHTML();
+
+?>
+--EXPECT--
+Another Link
diff --git a/ext/dom/tests/modern/xml/Element_outerHTML_writing.phpt b/ext/dom/tests/modern/xml/Element_outerHTML_writing.phpt
new file mode 100644
index 000000000000..f4bb3c8c4fce
--- /dev/null
+++ b/ext/dom/tests/modern/xml/Element_outerHTML_writing.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Test writing Element::$outerHTML on XML documents
+--EXTENSIONS--
+dom
+--FILE--
+");
+$dom->documentElement->firstChild->outerHTML = 'foo
bar
';
+echo $dom->saveXML(), "\n";
+
+$dom->documentElement->firstChild->outerHTML = $dom->documentElement->firstChild->outerHTML;
+$element = $dom->documentElement->firstChild->firstChild;
+echo $dom->saveXML(), "\n";
+
+$dom->documentElement->firstChild->outerHTML = 'tést';
+echo $dom->saveXML(), "\n";
+
+var_dump($element->tagName);
+
+$fragment = $dom->createDocumentFragment();
+$fragment->appendChild($dom->createElement('p'));
+$fragment->firstChild->outerHTML = 'bar';
+echo $dom->saveXML($fragment), "\n";
+
+?>
+--EXPECT--
+
+foo
bar
+
+foo
bar
+
+tést
+string(1) "p"
+bar
diff --git a/ext/dom/tests/modern/xml/Element_outerHTML_writing_errors.phpt b/ext/dom/tests/modern/xml/Element_outerHTML_writing_errors.phpt
new file mode 100644
index 000000000000..f7602539acac
--- /dev/null
+++ b/ext/dom/tests/modern/xml/Element_outerHTML_writing_errors.phpt
@@ -0,0 +1,25 @@
+--TEST--
+Test writing Element::$outerHTML on XML documents - error cases
+--EXTENSIONS--
+dom
+--FILE--
+');
+try {
+ $dom->documentElement->outerHTML = '';
+} catch (DOMException $e) {
+ echo $e->getMessage(), "\n";
+}
+
+$dom = Dom\XMLDocument::createFromString('');
+try {
+ $dom->documentElement->firstChild->outerHTML = '';
+} catch (DOMException $e) {
+ echo $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+Invalid Modification Error
+XML fragment is not well-formed
From f040af8945714522e53aae6f8a608347b57ff4be Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
Date: Sat, 14 Sep 2024 17:19:38 +0200
Subject: [PATCH 3/3] Rename inner_html_mixin.c to inner_outer_html_mixin.c
---
ext/dom/config.m4 | 2 +-
ext/dom/config.w32 | 2 +-
ext/dom/{inner_html_mixin.c => inner_outer_html_mixin.c} | 0
3 files changed, 2 insertions(+), 2 deletions(-)
rename ext/dom/{inner_html_mixin.c => inner_outer_html_mixin.c} (100%)
diff --git a/ext/dom/config.m4 b/ext/dom/config.m4
index b279bccd5bda..c6c67ced36e5 100644
--- a/ext/dom/config.m4
+++ b/ext/dom/config.m4
@@ -206,7 +206,7 @@ if test "$PHP_DOM" != "no"; then
html5_parser.c
html5_serializer.c
infra.c
- inner_html_mixin.c
+ inner_outer_html_mixin.c
namednodemap.c
namespace_compat.c
node.c
diff --git a/ext/dom/config.w32 b/ext/dom/config.w32
index 231f005895f5..1db4f6d11ba1 100644
--- a/ext/dom/config.w32
+++ b/ext/dom/config.w32
@@ -10,7 +10,7 @@ if (PHP_DOM == "yes") {
EXTENSION("dom", "php_dom.c attr.c document.c infra.c \
xml_document.c html_document.c xml_serializer.c html5_serializer.c html5_parser.c namespace_compat.c private_data.c \
domexception.c processinginstruction.c \
- cdatasection.c documentfragment.c domimplementation.c element.c inner_html_mixin.c \
+ cdatasection.c documentfragment.c domimplementation.c element.c inner_outer_html_mixin.c \
node.c characterdata.c documenttype.c \
entity.c nodelist.c html_collection.c text.c comment.c \
entityreference.c \
diff --git a/ext/dom/inner_html_mixin.c b/ext/dom/inner_outer_html_mixin.c
similarity index 100%
rename from ext/dom/inner_html_mixin.c
rename to ext/dom/inner_outer_html_mixin.c