From 3feb4656a4ae533b9cc97b9ddae05eba249274f6 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Mon, 24 Aug 2026 17:53:45 -0400 Subject: [PATCH] ext/dom: Register id attributes set via setAttribute() in XML documents getElementById() relies on the XML_ATTRIBUTE_ID flag, which dom_check_register_attribute_id() set only for HTML documents, leaving Dom\XMLDocument::getElementById() blind to ids assigned through setAttribute(), setAttributeNS() and setAttributeNode(). Drop the document-type restriction so modern XML documents register an un-namespaced id attribute; setIdAttribute(false) still unregisters and legacy DOMDocument is unchanged because every call site sits behind spec mode. setAttribute() reached the registration only when it created the attribute, so an id carried in from parsing stayed unregistered; it now runs for both branches. Closes GH-23801 --- NEWS | 2 + ext/dom/element.c | 10 +-- .../Document_getElementById_setAttribute.phpt | 68 +++++++++++++++++++ 3 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 ext/dom/tests/modern/xml/Document_getElementById_setAttribute.phpt diff --git a/NEWS b/NEWS index 8d7dd5088127..423631bbcea7 100644 --- a/NEWS +++ b/NEWS @@ -30,6 +30,8 @@ PHP NEWS . Fixed Dom\HTMLDocument giving attributes the namespace of their element when a fragment is parsed with an xlink, xml or xmlns context element. (Ilia Alshanetsky) + . getElementById() now finds elements whose id attribute was set with + setAttribute()/setAttributeNS() on Dom\XMLDocument. (Ilia Alshanetsky) - Fileinfo: . Upgrade to file 5.48. (Weilin Du) diff --git a/ext/dom/element.c b/ext/dom/element.c index 5fcffaf42055..41a605169ffb 100644 --- a/ext/dom/element.c +++ b/ext/dom/element.c @@ -393,8 +393,7 @@ static void dom_check_register_attribute_id(xmlAttrPtr attr, php_libxml_ref_obj { dom_mark_ids_modified(document); - if (attr->atype != XML_ATTRIBUTE_ID && attr->doc->type == XML_HTML_DOCUMENT_NODE && attr->ns == NULL && xmlStrEqual(attr->name, BAD_CAST "id")) { - /* To respect XML's ID behaviour, we only do this registration for HTML documents. */ + if (attr->atype != XML_ATTRIBUTE_ID && attr->ns == NULL && xmlStrEqual(attr->name, BAD_CAST "id")) { attr->atype = XML_ATTRIBUTE_ID; } } @@ -447,9 +446,10 @@ PHP_METHOD(DOMElement, setAttribute) xmlAddChild((xmlNodePtr) attr, node); } else { attr = xmlSetNsProp(nodep, NULL, name_processed, BAD_CAST value); - if (EXPECTED(attr != NULL)) { - dom_check_register_attribute_id(attr, intern->document); - } + } + + if (EXPECTED(attr != NULL)) { + dom_check_register_attribute_id(attr, intern->document); } if (name_processed != BAD_CAST name) { diff --git a/ext/dom/tests/modern/xml/Document_getElementById_setAttribute.phpt b/ext/dom/tests/modern/xml/Document_getElementById_setAttribute.phpt new file mode 100644 index 000000000000..91edde084b93 --- /dev/null +++ b/ext/dom/tests/modern/xml/Document_getElementById_setAttribute.phpt @@ -0,0 +1,68 @@ +--TEST-- +Dom\XMLDocument::getElementById() after setting id with setAttribute() +--EXTENSIONS-- +dom +--FILE-- +appendChild($dom->createElement("root")); +$a = $dom->createElement("a"); +$dom->documentElement->appendChild($a); + +echo "--- After parsing ---\n"; +var_dump($dom->getElementById("x")?->nodeName); + +echo "--- After setAttribute ---\n"; +$a->setAttribute("id", "x"); +var_dump($dom->getElementById("x")?->nodeName); +var_dump($dom->getElementById("y")?->nodeName); + +echo "--- After changing the id value with setAttribute ---\n"; +$a->setAttribute("id", "y"); +var_dump($dom->getElementById("x")?->nodeName); +var_dump($dom->getElementById("y")?->nodeName); + +echo "--- After setAttributeNS ---\n"; +$a->setAttributeNS(null, "id", "z"); +var_dump($dom->getElementById("y")?->nodeName); +var_dump($dom->getElementById("z")?->nodeName); + +echo "--- After removing the attribute ---\n"; +$a->removeAttribute("id"); +var_dump($dom->getElementById("z")?->nodeName); + +echo "--- After setIdAttribute ---\n"; +$a->setAttribute("id", "w"); +$a->setIdAttribute("id", false); +var_dump($dom->getElementById("w")?->nodeName); + +echo "--- Parsed id attribute changed with setAttribute ---\n"; +$parsed = Dom\XMLDocument::createFromString(''); +$b = $parsed->documentElement->firstElementChild; +var_dump($parsed->getElementById("p")?->nodeName); +$b->setAttribute("id", "q"); +var_dump($parsed->getElementById("p")?->nodeName); +var_dump($parsed->getElementById("q")?->nodeName); + +?> +--EXPECT-- +--- After parsing --- +NULL +--- After setAttribute --- +string(1) "a" +NULL +--- After changing the id value with setAttribute --- +NULL +string(1) "a" +--- After setAttributeNS --- +NULL +string(1) "a" +--- After removing the attribute --- +NULL +--- After setIdAttribute --- +NULL +--- Parsed id attribute changed with setAttribute --- +NULL +NULL +string(1) "b"