diff --git a/ext/dom/node.c b/ext/dom/node.c
index b291ccc99a308..bcf4ee487d38d 100644
--- a/ext/dom/node.c
+++ b/ext/dom/node.c
@@ -943,12 +943,20 @@ PHP_METHOD(DOMNode, insertBefore)
return;
}
}
+ new_child = xmlAddPrevSibling(refp, child);
+ if (UNEXPECTED(NULL == new_child)) {
+ goto cannot_add;
+ }
} else if (child->type == XML_DOCUMENT_FRAG_NODE) {
+ xmlNodePtr last = child->last;
new_child = _php_dom_insert_fragment(parentp, refp->prev, refp, child, intern, childobj);
- }
-
- if (new_child == NULL) {
+ dom_reconcile_ns_list(parentp->doc, new_child, last);
+ } else {
new_child = xmlAddPrevSibling(refp, child);
+ if (UNEXPECTED(NULL == new_child)) {
+ goto cannot_add;
+ }
+ dom_reconcile_ns(parentp->doc, new_child);
}
} else {
if (child->parent != NULL){
@@ -985,23 +993,28 @@ PHP_METHOD(DOMNode, insertBefore)
return;
}
}
+ new_child = xmlAddChild(parentp, child);
+ if (UNEXPECTED(NULL == new_child)) {
+ goto cannot_add;
+ }
} else if (child->type == XML_DOCUMENT_FRAG_NODE) {
+ xmlNodePtr last = child->last;
new_child = _php_dom_insert_fragment(parentp, parentp->last, NULL, child, intern, childobj);
- }
- if (new_child == NULL) {
+ dom_reconcile_ns_list(parentp->doc, new_child, last);
+ } else {
new_child = xmlAddChild(parentp, child);
+ if (UNEXPECTED(NULL == new_child)) {
+ goto cannot_add;
+ }
+ dom_reconcile_ns(parentp->doc, new_child);
}
}
- if (NULL == new_child) {
- zend_throw_error(NULL, "Cannot add newnode as the previous sibling of refnode");
- RETURN_THROWS();
- }
-
- dom_reconcile_ns(parentp->doc, new_child);
-
DOM_RET_OBJ(new_child, &ret, intern);
-
+ return;
+cannot_add:
+ zend_throw_error(NULL, "Cannot add newnode as the previous sibling of refnode");
+ RETURN_THROWS();
}
/* }}} end dom_node_insert_before */
@@ -1066,9 +1079,10 @@ PHP_METHOD(DOMNode, replaceChild)
xmlUnlinkNode(oldchild);
+ xmlNodePtr last = newchild->last;
newchild = _php_dom_insert_fragment(nodep, prevsib, nextsib, newchild, intern, newchildobj);
if (newchild) {
- dom_reconcile_ns(nodep->doc, newchild);
+ dom_reconcile_ns_list(nodep->doc, newchild, last);
}
} else if (oldchild != newchild) {
xmlDtdPtr intSubset = xmlGetIntSubset(nodep->doc);
@@ -1215,22 +1229,28 @@ PHP_METHOD(DOMNode, appendChild)
php_libxml_node_free_resource((xmlNodePtr) lastattr);
}
}
+ new_child = xmlAddChild(nodep, child);
+ if (UNEXPECTED(new_child == NULL)) {
+ goto cannot_add;
+ }
} else if (child->type == XML_DOCUMENT_FRAG_NODE) {
+ xmlNodePtr last = child->last;
new_child = _php_dom_insert_fragment(nodep, nodep->last, NULL, child, intern, childobj);
- }
-
- if (new_child == NULL) {
+ dom_reconcile_ns_list(nodep->doc, new_child, last);
+ } else {
new_child = xmlAddChild(nodep, child);
- if (new_child == NULL) {
- // TODO Convert to Error?
- php_error_docref(NULL, E_WARNING, "Couldn't append node");
- RETURN_FALSE;
+ if (UNEXPECTED(new_child == NULL)) {
+ goto cannot_add;
}
+ dom_reconcile_ns(nodep->doc, new_child);
}
- dom_reconcile_ns(nodep->doc, new_child);
-
DOM_RET_OBJ(new_child, &ret, intern);
+ return;
+cannot_add:
+ // TODO Convert to Error?
+ php_error_docref(NULL, E_WARNING, "Couldn't append node");
+ RETURN_FALSE;
}
/* }}} end dom_node_append_child */
diff --git a/ext/dom/parentnode.c b/ext/dom/parentnode.c
index c99a2a5a6622a..b7e8e3ba774e3 100644
--- a/ext/dom/parentnode.c
+++ b/ext/dom/parentnode.c
@@ -298,13 +298,14 @@ void dom_parent_node_append(dom_object *context, zval *nodes, int nodesc)
parentNode->children = newchild;
}
- parentNode->last = fragment->last;
+ xmlNodePtr last = fragment->last;
+ parentNode->last = last;
newchild->prev = prevsib;
dom_fragment_assign_parent_node(parentNode, fragment);
- dom_reconcile_ns(parentNode->doc, newchild);
+ dom_reconcile_ns_list(parentNode->doc, newchild, last);
}
xmlFree(fragment);
@@ -335,13 +336,14 @@ void dom_parent_node_prepend(dom_object *context, zval *nodes, int nodesc)
nextsib = parentNode->children;
if (newchild) {
+ xmlNodePtr last = fragment->last;
parentNode->children = newchild;
fragment->last->next = nextsib;
- nextsib->prev = fragment->last;
+ nextsib->prev = last;
dom_fragment_assign_parent_node(parentNode, fragment);
- dom_reconcile_ns(parentNode->doc, newchild);
+ dom_reconcile_ns_list(parentNode->doc, newchild, last);
}
xmlFree(fragment);
@@ -414,11 +416,13 @@ void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc)
newchild = fragment->children;
if (newchild) {
+ xmlNodePtr last = fragment->last;
+
/* Step 5: place fragment into the parent before viable_next_sibling */
dom_pre_insert(viable_next_sibling, parentNode, newchild, fragment);
dom_fragment_assign_parent_node(parentNode, fragment);
- dom_reconcile_ns(doc, newchild);
+ dom_reconcile_ns_list(doc, newchild, last);
}
xmlFree(fragment);
@@ -463,6 +467,8 @@ void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc)
newchild = fragment->children;
if (newchild) {
+ xmlNodePtr last = fragment->last;
+
/* Step 5: if viable_previous_sibling is null, set it to the parent's first child, otherwise viable_previous_sibling's next sibling */
if (!viable_previous_sibling) {
viable_previous_sibling = parentNode->children;
@@ -473,7 +479,7 @@ void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc)
dom_pre_insert(viable_previous_sibling, parentNode, newchild, fragment);
dom_fragment_assign_parent_node(parentNode, fragment);
- dom_reconcile_ns(doc, newchild);
+ dom_reconcile_ns_list(doc, newchild, last);
}
xmlFree(fragment);
diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c
index 1883767d2e48b..df20093221f16 100644
--- a/ext/dom/php_dom.c
+++ b/ext/dom/php_dom.c
@@ -1385,38 +1385,73 @@ void dom_set_old_ns(xmlDoc *doc, xmlNs *ns) {
}
/* }}} end dom_set_old_ns */
-void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */
+static void dom_reconcile_ns_internal(xmlDocPtr doc, xmlNodePtr nodep)
{
xmlNsPtr nsptr, nsdftptr, curns, prevns = NULL;
- if (nodep->type == XML_ELEMENT_NODE) {
- /* Following if block primarily used for inserting nodes created via createElementNS */
- if (nodep->nsDef != NULL) {
- curns = nodep->nsDef;
- while (curns) {
- nsdftptr = curns->next;
- if (curns->href != NULL) {
- if((nsptr = xmlSearchNsByHref(doc, nodep->parent, curns->href)) &&
- (curns->prefix == NULL || xmlStrEqual(nsptr->prefix, curns->prefix))) {
- curns->next = NULL;
- if (prevns == NULL) {
- nodep->nsDef = nsdftptr;
- } else {
- prevns->next = nsdftptr;
- }
- dom_set_old_ns(doc, curns);
- curns = prevns;
+ /* Following if block primarily used for inserting nodes created via createElementNS */
+ if (nodep->nsDef != NULL) {
+ curns = nodep->nsDef;
+ while (curns) {
+ nsdftptr = curns->next;
+ if (curns->href != NULL) {
+ if((nsptr = xmlSearchNsByHref(doc, nodep->parent, curns->href)) &&
+ (curns->prefix == NULL || xmlStrEqual(nsptr->prefix, curns->prefix))) {
+ curns->next = NULL;
+ if (prevns == NULL) {
+ nodep->nsDef = nsdftptr;
+ } else {
+ prevns->next = nsdftptr;
}
+ dom_set_old_ns(doc, curns);
+ curns = prevns;
}
- prevns = curns;
- curns = nsdftptr;
}
+ prevns = curns;
+ curns = nsdftptr;
}
+ }
+}
+
+void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */
+{
+ if (nodep->type == XML_ELEMENT_NODE) {
+ dom_reconcile_ns_internal(doc, nodep);
xmlReconciliateNs(doc, nodep);
}
}
/* }}} */
+static void dom_reconcile_ns_list_internal(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last)
+{
+ ZEND_ASSERT(nodep != NULL);
+ while (true) {
+ if (nodep->type == XML_ELEMENT_NODE) {
+ dom_reconcile_ns_internal(doc, nodep);
+ if (nodep->children) {
+ dom_reconcile_ns_list_internal(doc, nodep->children, nodep->last /* process the whole children list */);
+ }
+ }
+ if (nodep == last) {
+ break;
+ }
+ nodep = nodep->next;
+ }
+}
+
+void dom_reconcile_ns_list(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last)
+{
+ dom_reconcile_ns_list_internal(doc, nodep, last);
+ /* Outside of the recursion above because xmlReconciliateNs() performs its own recursion. */
+ while (true) {
+ xmlReconciliateNs(doc, nodep);
+ if (nodep == last) {
+ break;
+ }
+ nodep = nodep->next;
+ }
+}
+
/*
http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#ID-DocCrElNS
diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h
index fdfdd4e7a31ca..924d1397ca73a 100644
--- a/ext/dom/php_dom.h
+++ b/ext/dom/php_dom.h
@@ -110,6 +110,7 @@ int dom_check_qname(char *qname, char **localname, char **prefix, int uri_len, i
xmlNsPtr dom_get_ns(xmlNodePtr node, char *uri, int *errorcode, char *prefix);
void dom_set_old_ns(xmlDoc *doc, xmlNs *ns);
void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep);
+void dom_reconcile_ns_list(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last);
xmlNsPtr dom_get_nsdecl(xmlNode *node, xmlChar *localName);
void dom_normalize (xmlNodePtr nodep);
xmlNode *dom_get_elements_by_tag_name_ns_raw(xmlNodePtr nodep, char *ns, char *local, int *cur, int index);
diff --git a/ext/dom/tests/bug67440.phpt b/ext/dom/tests/bug67440.phpt
new file mode 100644
index 0000000000000..3e30f69b9ae4d
--- /dev/null
+++ b/ext/dom/tests/bug67440.phpt
@@ -0,0 +1,151 @@
+--TEST--
+Bug #67440 (append_node of a DOMDocumentFragment does not reconcile namespaces)
+--EXTENSIONS--
+dom
+--FILE--
+loadXML('');
+ $fragment = $document->createDocumentFragment();
+ $fragment->appendChild($document->createTextNode("\n"));
+ $fragment->appendChild($document->createElementNS('http://example/ns', 'myns:childNode', '1'));
+ $fragment->appendChild($document->createTextNode("\n"));
+ $fragment->appendChild($document->createElementNS('http://example/ns', 'myns:childNode', '2'));
+ $fragment->appendChild($document->createTextNode("\n"));
+ return array($document, $fragment);
+}
+
+function case1($method) {
+ list($document, $fragment) = createDocument();
+ $document->documentElement->{$method}($fragment);
+ echo $document->saveXML();
+}
+
+function case2($method) {
+ list($document, $fragment) = createDocument();
+ $childNodes = iterator_to_array($fragment->childNodes);
+ foreach ($childNodes as $childNode) {
+ $document->documentElement->{$method}($childNode);
+ }
+ echo $document->saveXML();
+}
+
+function case3($method) {
+ list($document, $fragment) = createDocument();
+ $fragment->removeChild($fragment->firstChild);
+ $document->documentElement->{$method}($fragment);
+ echo $document->saveXML();
+}
+
+function case4($method) {
+ list($document, $fragment) = createDocument();
+ $fragment->childNodes[1]->appendChild($document->createElementNS('http://example/ns2', 'myns2:childNode', '3'));
+ $document->documentElement->{$method}($fragment);
+ echo $document->saveXML();
+}
+
+echo "== appendChild ==\n";
+echo "-- fragment to document element --\n"; case1('appendChild'); echo "\n";
+echo "-- children manually document element --\n"; case2('appendChild'); echo "\n";
+echo "-- fragment to document where first element is not a text node --\n"; case3('appendChild'); echo "\n";
+echo "-- fragment with namespace declarations in children --\n"; case4('appendChild'); echo "\n";
+
+echo "== insertBefore ==\n";
+echo "-- fragment to document element --\n"; case1('insertBefore'); echo "\n";
+echo "-- children manually document element --\n"; case2('insertBefore'); echo "\n";
+echo "-- fragment to document where first element is not a text node --\n"; case3('insertBefore'); echo "\n";
+echo "-- fragment with namespace declarations in children --\n"; case4('insertBefore'); echo "\n";
+
+echo "== insertAfter ==\n";
+echo "-- fragment to document element --\n"; case1('insertBefore'); echo "\n";
+echo "-- children manually document element --\n"; case2('insertBefore'); echo "\n";
+echo "-- fragment to document where first element is not a text node --\n"; case3('insertBefore'); echo "\n";
+echo "-- fragment with namespace declarations in children --\n"; case4('insertBefore'); echo "\n";
+
+?>
+--EXPECT--
+== appendChild ==
+-- fragment to document element --
+
+
+1
+2
+
+
+-- children manually document element --
+
+
+1
+2
+
+
+-- fragment to document where first element is not a text node --
+
+1
+2
+
+
+-- fragment with namespace declarations in children --
+
+
+13
+2
+
+
+== insertBefore ==
+-- fragment to document element --
+
+
+1
+2
+
+
+-- children manually document element --
+
+
+1
+2
+
+
+-- fragment to document where first element is not a text node --
+
+1
+2
+
+
+-- fragment with namespace declarations in children --
+
+
+13
+2
+
+
+== insertAfter ==
+-- fragment to document element --
+
+
+1
+2
+
+
+-- children manually document element --
+
+
+1
+2
+
+
+-- fragment to document where first element is not a text node --
+
+1
+2
+
+
+-- fragment with namespace declarations in children --
+
+
+13
+2
+