diff --git a/NEWS b/NEWS index e0703f22545..58a8bbf7b08 100644 --- a/NEWS +++ b/NEWS @@ -25,6 +25,7 @@ PHP NEWS . Added DOMNode::getRootNode(). (nielsdos) . Added DOMElement::className and DOMElement::id. (nielsdos) . Added DOMParentNode::replaceChildren(). (nielsdos) + . Added DOMNode::isConnected and DOMNameSpaceNode::isConnected. (nielsdos) - FPM: . Added warning to log when fpm socket was not registered on the expected diff --git a/UPGRADING b/UPGRADING index 434fdc1fd56..59f60415211 100644 --- a/UPGRADING +++ b/UPGRADING @@ -268,6 +268,7 @@ PHP 8.3 UPGRADE NOTES This is not binary-safe at the moment because of underlying limitations of libxml2. . Added DOMParentNode::replaceChildren(). + . Added DOMNode::isConnected and DOMNameSpaceNode::isConnected. - JSON: . Added json_validate(), which returns whether the json is valid for diff --git a/ext/dom/document.c b/ext/dom/document.c index 182e3ce9033..1d7f62678b5 100644 --- a/ext/dom/document.c +++ b/ext/dom/document.c @@ -994,19 +994,6 @@ PHP_METHOD(DOMDocument, getElementsByTagNameNS) } /* }}} end dom_document_get_elements_by_tag_name_ns */ -static bool php_dom_is_node_attached(const xmlNode *node) -{ - ZEND_ASSERT(node != NULL); - node = node->parent; - while (node != NULL) { - if (node->type == XML_DOCUMENT_NODE || node->type == XML_HTML_DOCUMENT_NODE) { - return true; - } - node = node->parent; - } - return false; -} - /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-getElBId Since: DOM Level 2 */ @@ -1035,7 +1022,7 @@ PHP_METHOD(DOMDocument, getElementById) * ingrained in the library, and uses the cache for various purposes, it seems like a bad * idea and lost cause to fight it. Instead, we'll simply walk the tree upwards to check * if the node is attached to the document. */ - if (attrp && attrp->parent && php_dom_is_node_attached(attrp->parent)) { + if (attrp && attrp->parent && php_dom_is_node_connected(attrp->parent)) { DOM_RET_OBJ((xmlNodePtr) attrp->parent, &ret, intern); } else { RETVAL_NULL(); diff --git a/ext/dom/dom_properties.h b/ext/dom/dom_properties.h index 281a37197bb..6e86d3897b8 100644 --- a/ext/dom/dom_properties.h +++ b/ext/dom/dom_properties.h @@ -107,6 +107,7 @@ int dom_node_next_sibling_read(dom_object *obj, zval *retval); int dom_node_previous_element_sibling_read(dom_object *obj, zval *retval); int dom_node_next_element_sibling_read(dom_object *obj, zval *retval); int dom_node_attributes_read(dom_object *obj, zval *retval); +zend_result dom_node_is_connected_read(dom_object *obj, zval *retval); int dom_node_owner_document_read(dom_object *obj, zval *retval); int dom_node_namespace_uri_read(dom_object *obj, zval *retval); int dom_node_prefix_read(dom_object *obj, zval *retval); diff --git a/ext/dom/node.c b/ext/dom/node.c index d24e0af2819..386b787d826 100644 --- a/ext/dom/node.c +++ b/ext/dom/node.c @@ -52,6 +52,18 @@ zend_string *dom_node_get_node_name_attribute_or_element(const xmlNode *nodep) } } +bool php_dom_is_node_connected(const xmlNode *node) +{ + ZEND_ASSERT(node != NULL); + do { + if (node->type == XML_DOCUMENT_NODE || node->type == XML_HTML_DOCUMENT_NODE) { + return true; + } + node = node->parent; + } while (node != NULL); + return false; +} + /* {{{ nodeName string readonly=yes URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-F68D095 @@ -488,6 +500,25 @@ int dom_node_attributes_read(dom_object *obj, zval *retval) /* }}} */ +/* {{{ isConnected boolean +readonly=yes +URL: https://dom.spec.whatwg.org/#dom-node-isconnected +Since: +*/ +zend_result dom_node_is_connected_read(dom_object *obj, zval *retval) +{ + xmlNode *nodep = dom_object_get_node(obj); + + if (nodep == NULL) { + php_dom_throw_error(INVALID_STATE_ERR, 1); + return FAILURE; + } + + ZVAL_BOOL(retval, php_dom_is_node_connected(nodep)); + return SUCCESS; +} +/* }}} */ + /* {{{ ownerDocument DomDocument readonly=yes URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-node-ownerDoc diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index 5d1849856de..b9edaa2d187 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -642,6 +642,7 @@ PHP_MINIT_FUNCTION(dom) dom_register_prop_handler(&dom_node_prop_handlers, "previousSibling", sizeof("previousSibling")-1, dom_node_previous_sibling_read, NULL); dom_register_prop_handler(&dom_node_prop_handlers, "nextSibling", sizeof("nextSibling")-1, dom_node_next_sibling_read, NULL); dom_register_prop_handler(&dom_node_prop_handlers, "attributes", sizeof("attributes")-1, dom_node_attributes_read, NULL); + dom_register_prop_handler(&dom_node_prop_handlers, "isConnected", sizeof("isConnected")-1, dom_node_is_connected_read, NULL); dom_register_prop_handler(&dom_node_prop_handlers, "ownerDocument", sizeof("ownerDocument")-1, dom_node_owner_document_read, NULL); dom_register_prop_handler(&dom_node_prop_handlers, "namespaceURI", sizeof("namespaceURI")-1, dom_node_namespace_uri_read, NULL); dom_register_prop_handler(&dom_node_prop_handlers, "prefix", sizeof("prefix")-1, dom_node_prefix_read, dom_node_prefix_write); @@ -660,6 +661,7 @@ PHP_MINIT_FUNCTION(dom) dom_register_prop_handler(&dom_namespace_node_prop_handlers, "prefix", sizeof("prefix")-1, dom_node_prefix_read, NULL); dom_register_prop_handler(&dom_namespace_node_prop_handlers, "localName", sizeof("localName")-1, dom_node_local_name_read, NULL); dom_register_prop_handler(&dom_namespace_node_prop_handlers, "namespaceURI", sizeof("namespaceURI")-1, dom_node_namespace_uri_read, NULL); + dom_register_prop_handler(&dom_namespace_node_prop_handlers, "isConnected", sizeof("isConnected")-1, dom_node_is_connected_read, NULL); dom_register_prop_handler(&dom_namespace_node_prop_handlers, "ownerDocument", sizeof("ownerDocument")-1, dom_node_owner_document_read, NULL); dom_register_prop_handler(&dom_namespace_node_prop_handlers, "parentNode", sizeof("parentNode")-1, dom_node_parent_node_read, NULL); zend_hash_add_ptr(&classes, dom_namespace_node_class_entry->name, &dom_namespace_node_prop_handlers); diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h index b2c102fde45..91b51f8a148 100644 --- a/ext/dom/php_dom.h +++ b/ext/dom/php_dom.h @@ -150,7 +150,9 @@ xmlNodePtr php_dom_create_fake_namespace_decl(xmlNodePtr nodep, xmlNsPtr origina void php_dom_get_content_into_zval(const xmlNode *nodep, zval *target, bool default_is_null); zend_string *dom_node_concatenated_name_helper(size_t name_len, const char *name, size_t prefix_len, const char *prefix); zend_string *dom_node_get_node_name_attribute_or_element(const xmlNode *nodep); +bool php_dom_is_node_connected(const xmlNode *node); +/* parentnode */ void dom_parent_node_prepend(dom_object *context, zval *nodes, uint32_t nodesc); void dom_parent_node_append(dom_object *context, zval *nodes, uint32_t nodesc); void dom_parent_node_after(dom_object *context, zval *nodes, uint32_t nodesc); diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php index 4fb711856a4..bb6b918d5cd 100644 --- a/ext/dom/php_dom.stub.php +++ b/ext/dom/php_dom.stub.php @@ -326,6 +326,9 @@ class DOMNode /** @readonly */ public ?DOMNamedNodeMap $attributes; + /** @readonly */ + public bool $isConnected; + /** @readonly */ public ?DOMDocument $ownerDocument; @@ -419,6 +422,9 @@ class DOMNameSpaceNode /** @readonly */ public ?string $namespaceURI; + /** @readonly */ + public bool $isConnected; + /** @readonly */ public ?DOMDocument $ownerDocument; diff --git a/ext/dom/php_dom_arginfo.h b/ext/dom/php_dom_arginfo.h index caf7ee3d86f..d71ad1310b2 100644 --- a/ext/dom/php_dom_arginfo.h +++ b/ext/dom/php_dom_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 95dc217251318b625cad5b282b1adbb8eedaf1a5 */ + * Stub hash: 78e4089fa17aa0faa371917aeb48fa0dfe1819b0 */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_dom_import_simplexml, 0, 1, DOMElement, 0) ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0) @@ -1138,6 +1138,12 @@ static zend_class_entry *register_class_DOMNode(void) zend_declare_typed_property(class_entry, property_attributes_name, &property_attributes_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_attributes_class_DOMNamedNodeMap, 0, MAY_BE_NULL)); zend_string_release(property_attributes_name); + zval property_isConnected_default_value; + ZVAL_UNDEF(&property_isConnected_default_value); + zend_string *property_isConnected_name = zend_string_init("isConnected", sizeof("isConnected") - 1, 1); + zend_declare_typed_property(class_entry, property_isConnected_name, &property_isConnected_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL)); + zend_string_release(property_isConnected_name); + zval property_ownerDocument_default_value; ZVAL_UNDEF(&property_ownerDocument_default_value); zend_string *property_ownerDocument_name = zend_string_init("ownerDocument", sizeof("ownerDocument") - 1, 1); @@ -1222,6 +1228,12 @@ static zend_class_entry *register_class_DOMNameSpaceNode(void) zend_declare_typed_property(class_entry, property_namespaceURI_name, &property_namespaceURI_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); zend_string_release(property_namespaceURI_name); + zval property_isConnected_default_value; + ZVAL_UNDEF(&property_isConnected_default_value); + zend_string *property_isConnected_name = zend_string_init("isConnected", sizeof("isConnected") - 1, 1); + zend_declare_typed_property(class_entry, property_isConnected_name, &property_isConnected_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL)); + zend_string_release(property_isConnected_name); + zval property_ownerDocument_default_value; ZVAL_UNDEF(&property_ownerDocument_default_value); zend_string *property_ownerDocument_name = zend_string_init("ownerDocument", sizeof("ownerDocument") - 1, 1); diff --git a/ext/dom/tests/DOMNode_DOMNameSpaceNode_isConnected.phpt b/ext/dom/tests/DOMNode_DOMNameSpaceNode_isConnected.phpt new file mode 100644 index 00000000000..af75e5e2878 --- /dev/null +++ b/ext/dom/tests/DOMNode_DOMNameSpaceNode_isConnected.phpt @@ -0,0 +1,59 @@ +--TEST-- +DOMNode::isConnected and DOMNameSpaceNode::isConnected +--EXTENSIONS-- +dom +--FILE-- +loadXML(''); + +$docElement = $dom->documentElement; +$head = $docElement->firstChild; +$body = $head->nextSibling; + +echo "--- Created element not connected yet ---\n"; + +$p = $dom->createElement('p'); +var_dump($p->isConnected); + +echo "--- Appending and checking connection isn't broken for parents ---\n"; + +$body->appendChild($p); +var_dump($body->isConnected); +var_dump($p->isConnected); +$document = $docElement->parentNode; +var_dump($document->isConnected); +var_dump($dom->doctype->isConnected); + +echo "--- Indirect removal should set isConnected to false for affected nodes ---\n"; + +$body->remove(); +var_dump($p->isConnected); +var_dump($docElement->isConnected); +var_dump($body->isConnected); +var_dump($head->isConnected); +var_dump($dom->doctype->isConnected); + +echo "--- Empty document test ---\n"; + +$dom = new DOMDocument(); +var_dump($dom->isConnected); + +?> +--EXPECT-- +--- Created element not connected yet --- +bool(false) +--- Appending and checking connection isn't broken for parents --- +bool(true) +bool(true) +bool(true) +bool(true) +--- Indirect removal should set isConnected to false for affected nodes --- +bool(false) +bool(true) +bool(false) +bool(true) +bool(true) +--- Empty document test --- +bool(true) diff --git a/ext/dom/tests/bug69846.phpt b/ext/dom/tests/bug69846.phpt index 27c11199e7b..92b20d0fd34 100644 --- a/ext/dom/tests/bug69846.phpt +++ b/ext/dom/tests/bug69846.phpt @@ -30,7 +30,7 @@ foreach ($dataNodes AS $node) { ?> --EXPECTF-- int(3) -object(DOMText)#%d (21) { +object(DOMText)#%d (22) { ["wholeText"]=> string(3) " " @@ -64,6 +64,8 @@ object(DOMText)#%d (21) { NULL ["attributes"]=> NULL + ["isConnected"]=> + bool(false) ["ownerDocument"]=> string(22) "(object value omitted)" ["namespaceURI"]=> @@ -78,7 +80,7 @@ object(DOMText)#%d (21) { string(3) " " } -object(DOMElement)#7 (25) { +object(DOMElement)#7 (26) { ["schemaTypeInfo"]=> NULL ["tagName"]=> @@ -121,6 +123,8 @@ object(DOMElement)#7 (25) { NULL ["attributes"]=> string(22) "(object value omitted)" + ["isConnected"]=> + bool(false) ["ownerDocument"]=> string(22) "(object value omitted)" ["namespaceURI"]=> @@ -138,7 +142,7 @@ object(DOMElement)#7 (25) { Value C " } -object(DOMText)#%d (21) { +object(DOMText)#%d (22) { ["wholeText"]=> string(1) " " @@ -172,6 +176,8 @@ object(DOMText)#%d (21) { NULL ["attributes"]=> NULL + ["isConnected"]=> + bool(false) ["ownerDocument"]=> string(22) "(object value omitted)" ["namespaceURI"]=> diff --git a/ext/dom/tests/bug70359.phpt b/ext/dom/tests/bug70359.phpt index 697097bf882..442c1c5bc84 100644 --- a/ext/dom/tests/bug70359.phpt +++ b/ext/dom/tests/bug70359.phpt @@ -57,6 +57,7 @@ DOMNameSpaceNode Object [prefix] => [localName] => xmlns [namespaceURI] => http://www.sitemaps.org/schemas/sitemap/0.9 + [isConnected] => 1 [ownerDocument] => (object value omitted) [parentNode] => (object value omitted) ) @@ -73,6 +74,7 @@ DOMNameSpaceNode Object [prefix] => xsi [localName] => xsi [namespaceURI] => fooooooooooooooooooooo + [isConnected] => 1 [ownerDocument] => (object value omitted) [parentNode] => (object value omitted) ) diff --git a/ext/dom/tests/bug78577.phpt b/ext/dom/tests/bug78577.phpt index 2631efc1e20..e4f97bb1bb6 100644 --- a/ext/dom/tests/bug78577.phpt +++ b/ext/dom/tests/bug78577.phpt @@ -13,7 +13,7 @@ var_dump($attr); ?> --EXPECT-- -object(DOMNameSpaceNode)#3 (8) { +object(DOMNameSpaceNode)#3 (9) { ["nodeName"]=> string(5) "xmlns" ["nodeValue"]=> @@ -26,6 +26,8 @@ object(DOMNameSpaceNode)#3 (8) { string(5) "xmlns" ["namespaceURI"]=> string(19) "http://php.net/test" + ["isConnected"]=> + bool(true) ["ownerDocument"]=> string(22) "(object value omitted)" ["parentNode"]=> diff --git a/ext/dom/tests/bug80602_3.phpt b/ext/dom/tests/bug80602_3.phpt index 0e1b2dc796b..6b8fc9839ab 100644 --- a/ext/dom/tests/bug80602_3.phpt +++ b/ext/dom/tests/bug80602_3.phpt @@ -21,7 +21,7 @@ var_dump($target); ?> --EXPECTF-- barfoobaz -object(DOMElement)#3 (25) { +object(DOMElement)#3 (26) { ["schemaTypeInfo"]=> NULL ["tagName"]=> @@ -60,6 +60,8 @@ object(DOMElement)#3 (25) { NULL ["attributes"]=> string(22) "(object value omitted)" + ["isConnected"]=> + bool(true) ["ownerDocument"]=> string(22) "(object value omitted)" ["namespaceURI"]=> @@ -74,7 +76,7 @@ object(DOMElement)#3 (25) { string(0) "" } barfoobaz -object(DOMElement)#2 (25) { +object(DOMElement)#2 (26) { ["schemaTypeInfo"]=> NULL ["tagName"]=> @@ -113,6 +115,8 @@ object(DOMElement)#2 (25) { string(22) "(object value omitted)" ["attributes"]=> string(22) "(object value omitted)" + ["isConnected"]=> + bool(true) ["ownerDocument"]=> string(22) "(object value omitted)" ["namespaceURI"]=> diff --git a/ext/dom/tests/clone_nodes.phpt b/ext/dom/tests/clone_nodes.phpt index 1841c702caf..b76b67e911d 100644 --- a/ext/dom/tests/clone_nodes.phpt +++ b/ext/dom/tests/clone_nodes.phpt @@ -44,7 +44,7 @@ var_dump($barClone->parentNode); ?> --EXPECT-- -- Clone DOMNameSpaceNode -- -object(DOMNameSpaceNode)#3 (8) { +object(DOMNameSpaceNode)#3 (9) { ["nodeName"]=> string(5) "xmlns" ["nodeValue"]=> @@ -57,6 +57,8 @@ object(DOMNameSpaceNode)#3 (8) { string(5) "xmlns" ["namespaceURI"]=> string(19) "http://php.net/test" + ["isConnected"]=> + bool(true) ["ownerDocument"]=> string(22) "(object value omitted)" ["parentNode"]=> diff --git a/ext/dom/tests/domobject_debug_handler.phpt b/ext/dom/tests/domobject_debug_handler.phpt index 8318fc70ed1..5140026b16e 100644 --- a/ext/dom/tests/domobject_debug_handler.phpt +++ b/ext/dom/tests/domobject_debug_handler.phpt @@ -54,6 +54,7 @@ DOMDocument Object [previousSibling] => [nextSibling] => [attributes] => + [isConnected] => 1 [ownerDocument] => [namespaceURI] => [prefix] => diff --git a/ext/dom/tests/xpath_domnamespacenode.phpt b/ext/dom/tests/xpath_domnamespacenode.phpt index 97059c18e54..c15ee6adf63 100644 --- a/ext/dom/tests/xpath_domnamespacenode.phpt +++ b/ext/dom/tests/xpath_domnamespacenode.phpt @@ -17,7 +17,7 @@ var_dump($nodes->item(0)); ?> --EXPECT-- -object(DOMNameSpaceNode)#4 (8) { +object(DOMNameSpaceNode)#4 (9) { ["nodeName"]=> string(9) "xmlns:xml" ["nodeValue"]=> @@ -30,6 +30,8 @@ object(DOMNameSpaceNode)#4 (8) { string(3) "xml" ["namespaceURI"]=> string(36) "http://www.w3.org/XML/1998/namespace" + ["isConnected"]=> + bool(true) ["ownerDocument"]=> string(22) "(object value omitted)" ["parentNode"]=>