diff --git a/ext/dom/node.c b/ext/dom/node.c
index 88c058a6ac51..d54822d8077e 100644
--- a/ext/dom/node.c
+++ b/ext/dom/node.c
@@ -919,7 +919,7 @@ static bool dom_node_check_legacy_insertion_validity(xmlNodePtr parentp, xmlNode
}
/* Documents can never be a child. */
- if (child->type == XML_DOCUMENT_NODE || child->type == XML_HTML_DOCUMENT_NODE) {
+ if (child->type == XML_DOCUMENT_NODE || child->type == XML_HTML_DOCUMENT_NODE || child->type == XML_NOTATION_NODE) {
php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror);
return false;
}
diff --git a/ext/dom/parentnode/tree.c b/ext/dom/parentnode/tree.c
index aacc991cc735..5d068ee1bbd6 100644
--- a/ext/dom/parentnode/tree.c
+++ b/ext/dom/parentnode/tree.c
@@ -285,10 +285,10 @@ static bool dom_is_pre_insert_valid_without_step_1(php_libxml_ref_obj *document,
dom_hierarchy(parentNode, node) != SUCCESS
/* 4. If node is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException. */
|| node->type == XML_ATTRIBUTE_NODE
+ || node->type == XML_NOTATION_NODE
|| (php_dom_follow_spec_doc_ref(document) && (
node->type == XML_ENTITY_REF_NODE
|| node->type == XML_ENTITY_NODE
- || node->type == XML_NOTATION_NODE
|| node->type == XML_DOCUMENT_NODE
|| node->type == XML_HTML_DOCUMENT_NODE
|| node->type >= XML_ELEMENT_DECL))) {
diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c
index 0833d9a3c9be..e1d39f41bdc9 100644
--- a/ext/dom/php_dom.c
+++ b/ext/dom/php_dom.c
@@ -2629,6 +2629,13 @@ static xmlNodePtr dom_clone_helper(php_dom_libxml_ns_mapper *ns_mapper, xmlNodeP
xmlNodePtr dom_clone_node(php_dom_libxml_ns_mapper *ns_mapper, xmlNodePtr node, xmlDocPtr doc, bool recursive)
{
+ if (node->type == XML_NOTATION_NODE) {
+ const xmlEntity *notation = (const xmlEntity *) node;
+ xmlNodePtr clone = create_notation(NULL, notation->name, notation->ExternalID, notation->SystemID);
+ clone->doc = doc;
+ return clone;
+ }
+
if (node->type == XML_DTD_NODE) {
/* The behaviour w.r.t. the internal subset is implementation-defined according to DOM 3.
* This follows what e.g. Java and C# do: copy it regardless of the recursiveness.
diff --git a/ext/dom/tests/gh25268_notation_insert.phpt b/ext/dom/tests/gh25268_notation_insert.phpt
new file mode 100644
index 000000000000..1db202a2e6d2
--- /dev/null
+++ b/ext/dom/tests/gh25268_notation_insert.phpt
@@ -0,0 +1,37 @@
+--TEST--
+GH-25268 (Inserting a DOMNotation clone into a legacy document)
+--EXTENSIONS--
+dom
+--FILE--
+loadXML(']>');
+$notation = $doc->doctype->notations->getNamedItem('GIF');
+$root = $doc->documentElement;
+
+$tests = [
+ 'appendChild' => fn() => $root->appendChild(clone $notation),
+ 'insertBefore' => fn() => $root->insertBefore(clone $notation, $root->firstChild),
+ 'replaceChild' => fn() => $root->replaceChild(clone $notation, $root->firstChild),
+ 'append' => fn() => $root->append(clone $notation),
+ 'append original' => fn() => $root->append($notation),
+];
+
+foreach ($tests as $name => $test) {
+ try {
+ $test();
+ echo $name, ': inserted', PHP_EOL;
+ } catch (DOMException $e) {
+ echo $name, ': ', $e::class, ': ', $e->getMessage(), PHP_EOL;
+ }
+}
+
+echo $doc->saveXML($root), PHP_EOL;
+?>
+--EXPECT--
+appendChild: DOMException: Hierarchy Request Error
+insertBefore: DOMException: Hierarchy Request Error
+replaceChild: DOMException: Hierarchy Request Error
+append: DOMException: Hierarchy Request Error
+append original: DOMException: Hierarchy Request Error
+
diff --git a/ext/dom/tests/modern/xml/gh25268_notation.phpt b/ext/dom/tests/modern/xml/gh25268_notation.phpt
new file mode 100644
index 000000000000..b535abf44166
--- /dev/null
+++ b/ext/dom/tests/modern/xml/gh25268_notation.phpt
@@ -0,0 +1,29 @@
+--TEST--
+GH-25268 (Cloning Dom\Notation loses its node)
+--EXTENSIONS--
+dom
+--CREDITS--
+YuanchengJiang
+--FILE--
+]>');
+$notation = $dom->doctype->notations->getNamedItem("GIF");
+
+$clone = clone $notation;
+var_dump($clone->nodeName, $clone->ownerDocument === $dom, $clone->parentNode, $clone->isConnected);
+
+$clone2 = $notation->cloneNode();
+var_dump($clone2->nodeName, $clone2 === $clone);
+
+unset($dom, $notation);
+var_dump($clone->nodeName, $clone2->nodeName);
+?>
+--EXPECT--
+string(3) "GIF"
+bool(true)
+NULL
+bool(false)
+string(3) "GIF"
+bool(false)
+string(3) "GIF"
+string(3) "GIF"