diff --git a/NEWS b/NEWS index b72270513f3b6..ffa067224c378 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,11 @@ PHP NEWS - Date: . Fixed bug GH-11368 (Date modify returns invalid datetime). (Derick) +- DOM: + . Fixed bug GH-11625 (DOMElement::replaceWith() doesn't replace node with + DOMDocumentFragment but just deletes node or causes wrapping <> + depending on libxml2 version). (nielsdos) + - Fileinfo: . Fixed bug GH-11298 (finfo returns wrong mime type for xz files). (Anatol) diff --git a/ext/dom/parentnode.c b/ext/dom/parentnode.c index a9dfda59622b7..dba580ead7cd7 100644 --- a/ext/dom/parentnode.c +++ b/ext/dom/parentnode.c @@ -183,7 +183,15 @@ xmlNode* dom_zvals_to_fragment(php_libxml_ref_obj *document, xmlNode *contextNod goto err; } - if (newNode->parent != NULL) { + if (newNode->type == XML_DOCUMENT_FRAG_NODE) { + /* Unpack document fragment nodes, the behaviour differs for different libxml2 versions. */ + newNode = newNode->children; + if (UNEXPECTED(newNode == NULL)) { + /* No nodes to add, nothing to do here */ + continue; + } + xmlUnlinkNode(newNode); + } else if (newNode->parent != NULL) { xmlUnlinkNode(newNode); } @@ -370,7 +378,7 @@ static void dom_pre_insert(xmlNodePtr insertion_point, xmlNodePtr parentNode, xm insertion_point->prev->next = newchild; newchild->prev = insertion_point->prev; } - insertion_point->prev = newchild; + insertion_point->prev = fragment->last; if (parentNode->children == insertion_point) { parentNode->children = newchild; } @@ -555,14 +563,14 @@ void dom_child_replace_with(dom_object *context, zval *nodes, int nodesc) xmlNodePtr newchild = fragment->children; xmlDocPtr doc = parentNode->doc; + /* Unlink and free it unless it became a part of the fragment. */ + if (child->parent != fragment) { + xmlUnlinkNode(child); + } + if (newchild) { xmlNodePtr last = fragment->last; - /* Unlink and free it unless it became a part of the fragment. */ - if (child->parent != fragment) { - xmlUnlinkNode(child); - } - dom_pre_insert(insertion_point, parentNode, newchild, fragment); dom_fragment_assign_parent_node(parentNode, fragment); diff --git a/ext/dom/tests/gh11625.phpt b/ext/dom/tests/gh11625.phpt new file mode 100644 index 0000000000000..40e34d32b808d --- /dev/null +++ b/ext/dom/tests/gh11625.phpt @@ -0,0 +1,72 @@ +--TEST-- +GH-11625 (DOMElement::replaceWith() doesn't replace node with DOMDocumentFragment but just deletes node or causes wrapping <> depending on libxml2 version) +--EXTENSIONS-- +dom +--FILE-- + +
+ + XML; + + $dom = new DOMDocument(); + $dom->loadXML($html); + + $divs = iterator_to_array($dom->getElementsByTagName('div')->getIterator()); + $i = 0; + foreach ($divs as $div) { + $mutator($dom, $div, $i); + echo $dom->saveHTML(); + $i++; + } +} + +echo "--- Single replacement ---\n"; + +test(function($dom, $div, $i) { + $fragment = $dom->createDocumentFragment(); + $fragment->appendXML("

Hi $i!

"); + $div->replaceWith($fragment); +}); + +echo "--- Multiple replacement ---\n"; + +test(function($dom, $div, $i) { + $fragment = $dom->createDocumentFragment(); + $fragment->appendXML("

Hi $i!

"); + $div->replaceWith($fragment, $dom->createElement('x'), "hello"); +}); + +echo "--- Empty fragment replacement ---\n"; + +test(function($dom, $div, $i) { + $fragment = $dom->createDocumentFragment(); + $div->replaceWith($fragment); +}); + +?> +--EXPECT-- +--- Single replacement --- + +

Hi 0!

+ + +

Hi 0!

Hi 1!

+ +--- Multiple replacement --- + +

Hi 0!

hello
+ + +

Hi 0!

hello

Hi 1!

hello + +--- Empty fragment replacement --- + +
+ + + +