diff --git a/.github/actions/test-linux/action.yml b/.github/actions/test-linux/action.yml
index 9b7d0d100608f..36542cffb89a5 100644
--- a/.github/actions/test-linux/action.yml
+++ b/.github/actions/test-linux/action.yml
@@ -40,7 +40,7 @@ runs:
--offline \
--show-diff \
--show-slow 1000 \
- --set-timeout 120
+ --set-timeout 120 ext/dom
- uses: actions/upload-artifact@v3
if: always() && inputs.testArtifacts != null
with:
diff --git a/NEWS b/NEWS
index ae1d2c2cacaf4..e329624625dd3 100644
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,10 @@ PHP NEWS
. Fixed bug GH-11507 (String concatenation performance regression in 8.3).
(nielsdos)
+- DOM:
+ . Implemented DOMDocument::adoptNode(). Previously this always threw a
+ "not yet implemented" exception. (nielsdos)
+
- Fileinfo:
. Fix GH-11408 (Unable to build PHP 8.3.0 alpha 1 / fileinfo extension).
(nielsdos)
diff --git a/UPGRADING b/UPGRADING
index 12189958faaeb..bc7e50760be7b 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -130,6 +130,8 @@ PHP 8.3 UPGRADE NOTES
- Dom:
. Changed DOMCharacterData::appendData() tentative return type to true.
+ . Implemented DOMDocument::adoptNode(). Previously this always threw a
+ "not yet implemented" exception.
- Gd:
. Changed imagerotate signature, removed the `ignore_transparent` argument
diff --git a/ext/dom/document.c b/ext/dom/document.c
index 1b26c9c7bfc73..fa32452c8540d 100644
--- a/ext/dom/document.c
+++ b/ext/dom/document.c
@@ -1051,18 +1051,73 @@ PHP_METHOD(DOMDocument, getElementById)
}
/* }}} end dom_document_get_element_by_id */
+static void php_dom_transfer_document_ref(xmlNodePtr node, dom_object *dom_object_document, xmlDocPtr document)
+{
+ if (node->children) {
+ php_dom_transfer_document_ref(node->children, dom_object_document, document);
+ }
+ while (node) {
+ php_libxml_node_ptr *iteration_object_ptr = node->_private;
+ if (iteration_object_ptr) {
+ php_libxml_node_object *iteration_object = iteration_object_ptr->_private;
+ ZEND_ASSERT(iteration_object != NULL);
+ /* Must increase refcount first because we could be the last reference holder, and the document may be equal. */
+ dom_object_document->document->refcount++;
+ php_libxml_decrement_doc_ref(iteration_object);
+ iteration_object->document = dom_object_document->document;
+ }
+ node = node->next;
+ }
+}
+
/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-Document3-adoptNode
Since: DOM Level 3
+Modern spec URL: https://dom.spec.whatwg.org/#dom-document-adoptnode
*/
PHP_METHOD(DOMDocument, adoptNode)
{
- zval *nodep = NULL;
-
- if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &nodep, dom_node_class_entry) == FAILURE) {
+ zval *node_zval;
+ if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &node_zval, dom_node_class_entry) == FAILURE) {
RETURN_THROWS();
}
- DOM_NOT_IMPLEMENTED();
+ xmlNodePtr nodep;
+ dom_object *dom_object_nodep;
+ DOM_GET_OBJ(nodep, node_zval, xmlNodePtr, dom_object_nodep);
+
+ if (UNEXPECTED(nodep->type == XML_DOCUMENT_NODE
+ || nodep->type == XML_HTML_DOCUMENT_NODE
+ || nodep->type == XML_DOCUMENT_TYPE_NODE
+ || nodep->type == XML_DTD_NODE
+ || nodep->type == XML_ENTITY_NODE
+ || nodep->type == XML_NOTATION_NODE)) {
+ php_dom_throw_error(NOT_SUPPORTED_ERR, true);
+ return;
+ }
+
+ xmlDocPtr new_document;
+ dom_object *dom_object_new_document;
+ zval *new_document_zval = ZEND_THIS;
+ DOM_GET_OBJ(new_document, new_document_zval, xmlDocPtr, dom_object_new_document);
+
+ php_libxml_invalidate_node_list_cache_from_doc(nodep->doc);
+
+ if (nodep->doc != new_document) {
+ php_libxml_invalidate_node_list_cache_from_doc(new_document);
+
+ /* Note for ATTRIBUTE_NODE: specified is always true in ext/dom,
+ * and since this unlink it; the owner element will be unset (i.e. parentNode). */
+ int ret = xmlDOMWrapAdoptNode(NULL, nodep->doc, nodep, new_document, NULL, /* options, unused */ 0);
+ if (UNEXPECTED(ret != 0)) {
+ RETURN_FALSE;
+ }
+
+ php_dom_transfer_document_ref(nodep, dom_object_new_document, new_document);
+ } else {
+ xmlUnlinkNode(nodep);
+ }
+
+ RETURN_OBJ_COPY(&dom_object_nodep->std);
}
/* }}} end dom_document_adopt_node */
diff --git a/ext/dom/tests/DOMDocument_adoptNode.phpt b/ext/dom/tests/DOMDocument_adoptNode.phpt
index fbc6ff2205e8c..e2f5c0bf431ee 100644
--- a/ext/dom/tests/DOMDocument_adoptNode.phpt
+++ b/ext/dom/tests/DOMDocument_adoptNode.phpt
@@ -1,18 +1,141 @@
--TEST--
-DOMDocument::adoptNode not implemented
+Tests DOMDocument::adoptNode()
--EXTENSIONS--
dom
--FILE--
loadXML("
hixworld
"); +$doc2 = new DOMDocument(); +$doc2->loadXML(""); +$b_tag_element = $doc1->firstChild->firstChild; +$i_tag_element = $b_tag_element->lastChild; + +echo "-- Owner document check before adopting --\n"; +var_dump($i_tag_element->ownerDocument === $doc1); +var_dump($i_tag_element->ownerDocument === $doc2); + +echo "-- Trying to append child from other document --\n"; try { - $dom->adoptNode($dom->documentElement); -} catch (\Error $e) { - echo $e->getMessage() . \PHP_EOL; + $doc2->firstChild->appendChild($b_tag_element); // Should fail because it's another document +} catch (\DOMException $e) { + echo $e->getMessage(), "\n"; } + +echo "-- Adopting --\n"; +$adopted = $doc2->adoptNode($b_tag_element); +var_dump($adopted->textContent); +var_dump($doc1->saveXML()); +var_dump($doc2->saveXML()); + +echo "-- Appending the adopted node --\n"; + +$doc2->firstChild->appendChild($adopted); +var_dump($doc2->saveXML()); +var_dump($i_tag_element->ownerDocument === $doc1); +var_dump($i_tag_element->ownerDocument === $doc2); + +echo "-- Adopt node to the original document --\n"; + +$adopted = $doc1->adoptNode($doc1->firstChild->firstChild); +var_dump($adopted->textContent); +var_dump($doc1->saveXML()); + +echo "-- Adopt a document --\n"; + +try { + $doc1->adoptNode($doc1); +} catch (\DOMException $e) { + echo $e->getMessage(), "\n"; +} + +echo "-- Adopt an attribute --\n"; + +$doc3 = new DOMDocument(); +$doc3->loadXML('hi
'); +$attribute = $doc3->firstChild->attributes->item(0); +var_dump($attribute->parentNode !== NULL); +$adopted = $doc3->adoptNode($attribute); +var_dump($adopted->parentNode === NULL); + +echo "-- Adopt an entity reference --\n"; + +$doc4 = new DOMDocument(); +$doc4->loadXML(<<<'XML' + +hi'> ]> + +XML, LIBXML_NOENT); +$p_tag_element = $doc4->firstChild->nextSibling; +$entity_reference = $doc4->createEntityReference('my_entity'); +$p_tag_element->appendChild($entity_reference); +var_dump($doc4->saveXML()); +$doc3->adoptNode($entity_reference); +var_dump($doc4->saveXML()); +$doc3->firstChild->appendChild($entity_reference); +var_dump($doc3->saveXML()); + +echo "-- Adopt a node and destroy the new document --\n"; +$doc1 = new DOMDocument(); +$doc1->appendChild($doc1->createElement('child')); +$doc2 = new DOMDocument(); +$doc2->appendChild($doc2->createElement('container')); +$doc2->documentElement->appendChild($child = $doc2->adoptNode($doc1->documentElement)); +unset($doc2); +var_dump($child->nodeName); +unset($doc1); +var_dump($child->nodeName); + ?> --EXPECT-- -Not yet implemented +-- Owner document check before adopting -- +bool(true) +bool(false) +-- Trying to append child from other document -- +Wrong Document Error +-- Adopting -- +string(3) "hix" +string(35) " +world
+" +string(29) " + +" +-- Appending the adopted node -- +string(62) " +&my_entity;
+" +string(188) " +hi"> +]> + +" +string(43) " +hi&my_entity;
+" +-- Adopt a node and destroy the new document -- +string(5) "child" +string(5) "child"