Skip to content

Commit e8fb0ed

Browse files
committed
Merge branch 'PHP-8.2'
* PHP-8.2: Fix bug #77686: Removed elements are still returned by getElementById Fix bug #81642: DOMChildNode::replaceWith() bug when replacing a node with itself Fix bug #67440: append_node of a DOMDocumentFragment does not reconcile namespaces
2 parents 3da4156 + 5b79c53 commit e8fb0ed

File tree

9 files changed

+433
-64
lines changed

9 files changed

+433
-64
lines changed

ext/dom/document.c

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,19 @@ PHP_METHOD(DOMDocument, getElementsByTagNameNS)
10051005
}
10061006
/* }}} end dom_document_get_elements_by_tag_name_ns */
10071007

1008+
static bool php_dom_is_node_attached(const xmlNode *node)
1009+
{
1010+
ZEND_ASSERT(node != NULL);
1011+
node = node->parent;
1012+
while (node != NULL) {
1013+
if (node->type == XML_DOCUMENT_NODE || node->type == XML_HTML_DOCUMENT_NODE) {
1014+
return true;
1015+
}
1016+
node = node->parent;
1017+
}
1018+
return false;
1019+
}
1020+
10081021
/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-getElBId
10091022
Since: DOM Level 2
10101023
*/
@@ -1027,7 +1040,13 @@ PHP_METHOD(DOMDocument, getElementById)
10271040

10281041
attrp = xmlGetID(docp, (xmlChar *) idname);
10291042

1030-
if (attrp && attrp->parent) {
1043+
/* From the moment an ID is created, libxml2's behaviour is to cache that element, even
1044+
* if that element is not yet attached to the document. Similarly, only upon destruction of
1045+
* the element the ID is actually removed by libxml2. Since libxml2 has such behaviour deeply
1046+
* ingrained in the library, and uses the cache for various purposes, it seems like a bad
1047+
* idea and lost cause to fight it. Instead, we'll simply walk the tree upwards to check
1048+
* if the node is attached to the document. */
1049+
if (attrp && attrp->parent && php_dom_is_node_attached(attrp->parent)) {
10311050
DOM_RET_OBJ((xmlNodePtr) attrp->parent, &ret, intern);
10321051
} else {
10331052
RETVAL_NULL();

ext/dom/element.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,7 +1229,7 @@ PHP_METHOD(DOMElement, prepend)
12291229
}
12301230
/* }}} end DOMElement::prepend */
12311231

1232-
/* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-prepend
1232+
/* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-replacechildren
12331233
Since: DOM Living Standard (DOM4)
12341234
*/
12351235
PHP_METHOD(DOMElement, replaceWith)
@@ -1246,8 +1246,7 @@ PHP_METHOD(DOMElement, replaceWith)
12461246
id = ZEND_THIS;
12471247
DOM_GET_OBJ(context, id, xmlNodePtr, intern);
12481248

1249-
dom_parent_node_after(intern, args, argc);
1250-
dom_child_node_remove(intern);
1249+
dom_child_replace_with(intern, args, argc);
12511250
}
12521251
/* }}} end DOMElement::prepend */
12531252

ext/dom/node.c

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -949,12 +949,20 @@ PHP_METHOD(DOMNode, insertBefore)
949949
return;
950950
}
951951
}
952+
new_child = xmlAddPrevSibling(refp, child);
953+
if (UNEXPECTED(NULL == new_child)) {
954+
goto cannot_add;
955+
}
952956
} else if (child->type == XML_DOCUMENT_FRAG_NODE) {
957+
xmlNodePtr last = child->last;
953958
new_child = _php_dom_insert_fragment(parentp, refp->prev, refp, child, intern, childobj);
954-
}
955-
956-
if (new_child == NULL) {
959+
dom_reconcile_ns_list(parentp->doc, new_child, last);
960+
} else {
957961
new_child = xmlAddPrevSibling(refp, child);
962+
if (UNEXPECTED(NULL == new_child)) {
963+
goto cannot_add;
964+
}
965+
dom_reconcile_ns(parentp->doc, new_child);
958966
}
959967
} else {
960968
if (child->parent != NULL){
@@ -991,23 +999,28 @@ PHP_METHOD(DOMNode, insertBefore)
991999
return;
9921000
}
9931001
}
1002+
new_child = xmlAddChild(parentp, child);
1003+
if (UNEXPECTED(NULL == new_child)) {
1004+
goto cannot_add;
1005+
}
9941006
} else if (child->type == XML_DOCUMENT_FRAG_NODE) {
1007+
xmlNodePtr last = child->last;
9951008
new_child = _php_dom_insert_fragment(parentp, parentp->last, NULL, child, intern, childobj);
996-
}
997-
if (new_child == NULL) {
1009+
dom_reconcile_ns_list(parentp->doc, new_child, last);
1010+
} else {
9981011
new_child = xmlAddChild(parentp, child);
1012+
if (UNEXPECTED(NULL == new_child)) {
1013+
goto cannot_add;
1014+
}
1015+
dom_reconcile_ns(parentp->doc, new_child);
9991016
}
10001017
}
10011018

1002-
if (NULL == new_child) {
1003-
zend_throw_error(NULL, "Cannot add newnode as the previous sibling of refnode");
1004-
RETURN_THROWS();
1005-
}
1006-
1007-
dom_reconcile_ns(parentp->doc, new_child);
1008-
10091019
DOM_RET_OBJ(new_child, &ret, intern);
1010-
1020+
return;
1021+
cannot_add:
1022+
zend_throw_error(NULL, "Cannot add newnode as the previous sibling of refnode");
1023+
RETURN_THROWS();
10111024
}
10121025
/* }}} end dom_node_insert_before */
10131026

@@ -1072,9 +1085,10 @@ PHP_METHOD(DOMNode, replaceChild)
10721085

10731086
xmlUnlinkNode(oldchild);
10741087

1088+
xmlNodePtr last = newchild->last;
10751089
newchild = _php_dom_insert_fragment(nodep, prevsib, nextsib, newchild, intern, newchildobj);
10761090
if (newchild) {
1077-
dom_reconcile_ns(nodep->doc, newchild);
1091+
dom_reconcile_ns_list(nodep->doc, newchild, last);
10781092
}
10791093
} else if (oldchild != newchild) {
10801094
xmlDtdPtr intSubset = xmlGetIntSubset(nodep->doc);
@@ -1223,24 +1237,30 @@ PHP_METHOD(DOMNode, appendChild)
12231237
php_libxml_node_free_resource((xmlNodePtr) lastattr);
12241238
}
12251239
}
1240+
new_child = xmlAddChild(nodep, child);
1241+
if (UNEXPECTED(new_child == NULL)) {
1242+
goto cannot_add;
1243+
}
12261244
} else if (child->type == XML_DOCUMENT_FRAG_NODE) {
1245+
xmlNodePtr last = child->last;
12271246
new_child = _php_dom_insert_fragment(nodep, nodep->last, NULL, child, intern, childobj);
1228-
}
1229-
1230-
if (new_child == NULL) {
1247+
dom_reconcile_ns_list(nodep->doc, new_child, last);
1248+
} else {
12311249
new_child = xmlAddChild(nodep, child);
1232-
if (new_child == NULL) {
1233-
// TODO Convert to Error?
1234-
php_error_docref(NULL, E_WARNING, "Couldn't append node");
1235-
RETURN_FALSE;
1250+
if (UNEXPECTED(new_child == NULL)) {
1251+
goto cannot_add;
12361252
}
1253+
dom_reconcile_ns(nodep->doc, new_child);
12371254
}
12381255

1239-
dom_reconcile_ns(nodep->doc, new_child);
1240-
12411256
php_libxml_invalidate_node_list_cache_from_doc(nodep->doc);
12421257

12431258
DOM_RET_OBJ(new_child, &ret, intern);
1259+
return;
1260+
cannot_add:
1261+
// TODO Convert to Error?
1262+
php_error_docref(NULL, E_WARNING, "Couldn't append node");
1263+
RETURN_FALSE;
12441264
}
12451265
/* }}} end dom_node_append_child */
12461266

ext/dom/parentnode.c

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -298,13 +298,14 @@ void dom_parent_node_append(dom_object *context, zval *nodes, int nodesc)
298298
parentNode->children = newchild;
299299
}
300300

301-
parentNode->last = fragment->last;
301+
xmlNodePtr last = fragment->last;
302+
parentNode->last = last;
302303

303304
newchild->prev = prevsib;
304305

305306
dom_fragment_assign_parent_node(parentNode, fragment);
306307

307-
dom_reconcile_ns(parentNode->doc, newchild);
308+
dom_reconcile_ns_list(parentNode->doc, newchild, last);
308309
}
309310

310311
xmlFree(fragment);
@@ -337,13 +338,14 @@ void dom_parent_node_prepend(dom_object *context, zval *nodes, int nodesc)
337338
nextsib = parentNode->children;
338339

339340
if (newchild) {
341+
xmlNodePtr last = fragment->last;
340342
parentNode->children = newchild;
341343
fragment->last->next = nextsib;
342-
nextsib->prev = fragment->last;
344+
nextsib->prev = last;
343345

344346
dom_fragment_assign_parent_node(parentNode, fragment);
345347

346-
dom_reconcile_ns(parentNode->doc, newchild);
348+
dom_reconcile_ns_list(parentNode->doc, newchild, last);
347349
}
348350

349351
xmlFree(fragment);
@@ -418,11 +420,13 @@ void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc)
418420
newchild = fragment->children;
419421

420422
if (newchild) {
423+
xmlNodePtr last = fragment->last;
424+
421425
/* Step 5: place fragment into the parent before viable_next_sibling */
422426
dom_pre_insert(viable_next_sibling, parentNode, newchild, fragment);
423427

424428
dom_fragment_assign_parent_node(parentNode, fragment);
425-
dom_reconcile_ns(doc, newchild);
429+
dom_reconcile_ns_list(doc, newchild, last);
426430
}
427431

428432
xmlFree(fragment);
@@ -469,6 +473,8 @@ void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc)
469473
newchild = fragment->children;
470474

471475
if (newchild) {
476+
xmlNodePtr last = fragment->last;
477+
472478
/* Step 5: if viable_previous_sibling is null, set it to the parent's first child, otherwise viable_previous_sibling's next sibling */
473479
if (!viable_previous_sibling) {
474480
viable_previous_sibling = parentNode->children;
@@ -479,41 +485,52 @@ void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc)
479485
dom_pre_insert(viable_previous_sibling, parentNode, newchild, fragment);
480486

481487
dom_fragment_assign_parent_node(parentNode, fragment);
482-
dom_reconcile_ns(doc, newchild);
488+
dom_reconcile_ns_list(doc, newchild, last);
483489
}
484490

485491
xmlFree(fragment);
486492
}
487493

488-
void dom_child_node_remove(dom_object *context)
494+
static zend_result dom_child_removal_preconditions(const xmlNodePtr child, int stricterror)
489495
{
490-
xmlNode *child = dom_object_get_node(context);
491-
xmlNodePtr children;
492-
int stricterror;
493-
494-
stricterror = dom_get_strict_error(context->document);
495-
496496
if (dom_node_is_read_only(child) == SUCCESS ||
497497
(child->parent != NULL && dom_node_is_read_only(child->parent) == SUCCESS)) {
498498
php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror);
499-
return;
499+
return FAILURE;
500500
}
501501

502502
if (!child->parent) {
503503
php_dom_throw_error(NOT_FOUND_ERR, stricterror);
504-
return;
504+
return FAILURE;
505505
}
506506

507507
if (dom_node_children_valid(child->parent) == FAILURE) {
508-
return;
508+
return FAILURE;
509509
}
510510

511-
children = child->parent->children;
511+
xmlNodePtr children = child->parent->children;
512512
if (!children) {
513513
php_dom_throw_error(NOT_FOUND_ERR, stricterror);
514+
return FAILURE;
515+
}
516+
517+
return SUCCESS;
518+
}
519+
520+
void dom_child_node_remove(dom_object *context)
521+
{
522+
xmlNode *child = dom_object_get_node(context);
523+
xmlNodePtr children;
524+
int stricterror;
525+
526+
stricterror = dom_get_strict_error(context->document);
527+
528+
if (UNEXPECTED(dom_child_removal_preconditions(child, stricterror) != SUCCESS)) {
514529
return;
515530
}
516531

532+
children = child->parent->children;
533+
517534
php_libxml_invalidate_node_list_cache_from_doc(context->document->ptr);
518535

519536
while (children) {
@@ -527,4 +544,41 @@ void dom_child_node_remove(dom_object *context)
527544
php_dom_throw_error(NOT_FOUND_ERR, stricterror);
528545
}
529546

547+
void dom_child_replace_with(dom_object *context, zval *nodes, int nodesc)
548+
{
549+
xmlNodePtr child = dom_object_get_node(context);
550+
xmlNodePtr parentNode = child->parent;
551+
552+
int stricterror = dom_get_strict_error(context->document);
553+
if (UNEXPECTED(dom_child_removal_preconditions(child, stricterror) != SUCCESS)) {
554+
return;
555+
}
556+
557+
xmlNodePtr insertion_point = child->next;
558+
559+
xmlNodePtr fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc);
560+
if (UNEXPECTED(fragment == NULL)) {
561+
return;
562+
}
563+
564+
xmlNodePtr newchild = fragment->children;
565+
xmlDocPtr doc = parentNode->doc;
566+
567+
if (newchild) {
568+
xmlNodePtr last = fragment->last;
569+
570+
/* Unlink and free it unless it became a part of the fragment. */
571+
if (child->parent != fragment) {
572+
xmlUnlinkNode(child);
573+
}
574+
575+
dom_pre_insert(insertion_point, parentNode, newchild, fragment);
576+
577+
dom_fragment_assign_parent_node(parentNode, fragment);
578+
dom_reconcile_ns_list(doc, newchild, last);
579+
}
580+
581+
xmlFree(fragment);
582+
}
583+
530584
#endif

0 commit comments

Comments
 (0)