diff --git a/ext/dom/document.c b/ext/dom/document.c index 45db1d81feecf..56b878ca0298a 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 32f2419323833..4f81a9d3fdccd 100644 --- a/ext/dom/dom_properties.h +++ b/ext/dom/dom_properties.h @@ -103,6 +103,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 14fa030ccda8b..77d4aefdef95a 100644 --- a/ext/dom/node.c +++ b/ext/dom/node.c @@ -30,6 +30,18 @@ * Since: */ +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 453b4d95ecc91..12111db33d4d3 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 fbe2e48fb58ee..2f22e60c66776 100644 --- a/ext/dom/php_dom.h +++ b/ext/dom/php_dom.h @@ -148,7 +148,9 @@ zend_object_iterator *php_dom_get_iterator(zend_class_entry *ce, zval *object, i void dom_set_doc_classmap(php_libxml_ref_obj *document, zend_class_entry *basece, zend_class_entry *ce); xmlNodePtr php_dom_create_fake_namespace_decl(xmlNodePtr nodep, xmlNsPtr original, zval *return_value, dom_object *parent_intern); void php_dom_get_content_into_zval(const xmlNode *nodep, zval *target, bool default_is_null); +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 741f7bf1d9e10..fb2eca97cbd8e 100644 --- a/ext/dom/php_dom.stub.php +++ b/ext/dom/php_dom.stub.php @@ -323,6 +323,9 @@ class DOMNode /** @readonly */ public ?DOMNamedNodeMap $attributes; + /** @readonly */ + public bool $isConnected; + /** @readonly */ public ?DOMDocument $ownerDocument; @@ -412,6 +415,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 1459a9891bb08..6fda73c65a98e 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: 00d59fd45c44eb14bbf8f51ee4f61e0464786d69 */ + * Stub hash: 742a27d82c195337887d3a4f2ba74e008e06835a */ 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) @@ -1106,6 +1106,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); @@ -1190,6 +1196,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 0000000000000..af75e5e287826 --- /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 7655d203fa22f..69a963be8b729 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 (23) { +object(DOMElement)#%d (24) { ["schemaTypeInfo"]=> NULL ["tagName"]=> @@ -117,6 +119,8 @@ object(DOMElement)#7 (23) { NULL ["attributes"]=> string(22) "(object value omitted)" + ["isConnected"]=> + bool(false) ["ownerDocument"]=> string(22) "(object value omitted)" ["namespaceURI"]=> @@ -134,7 +138,7 @@ object(DOMElement)#7 (23) { Value C " } -object(DOMText)#%d (21) { +object(DOMText)#%d (22) { ["wholeText"]=> string(1) " " @@ -168,6 +172,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 697097bf88287..442c1c5bc84f0 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 2631efc1e206c..e4f97bb1bb692 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 f9bf67e778da5..0c2cc39dacd63 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 (23) { +object(DOMElement)#3 (24) { ["schemaTypeInfo"]=> NULL ["tagName"]=> @@ -56,6 +56,8 @@ object(DOMElement)#3 (23) { NULL ["attributes"]=> string(22) "(object value omitted)" + ["isConnected"]=> + bool(true) ["ownerDocument"]=> string(22) "(object value omitted)" ["namespaceURI"]=> @@ -70,7 +72,7 @@ object(DOMElement)#3 (23) { string(0) "" } barfoobaz -object(DOMElement)#2 (23) { +object(DOMElement)#2 (24) { ["schemaTypeInfo"]=> NULL ["tagName"]=> @@ -105,6 +107,8 @@ object(DOMElement)#2 (23) { 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 1841c702caf8d..b76b67e911d8f 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 8318fc70ed17e..5140026b16e15 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 97059c18e54da..c15ee6adf63a3 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"]=>