mediawiki-extensions-Discus.../includes/ImmutableRange.php

831 lines
24 KiB
PHP
Raw Normal View History

<?php
namespace MediaWiki\Extension\DiscussionTools;
use DOMException;
use RuntimeException;
use Wikimedia\Assert\Assert;
use Wikimedia\Parsoid\DOM\CharacterData;
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
use Wikimedia\Parsoid\DOM\Comment;
use Wikimedia\Parsoid\DOM\Document;
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
use Wikimedia\Parsoid\DOM\DocumentFragment;
use Wikimedia\Parsoid\DOM\DocumentType;
use Wikimedia\Parsoid\DOM\Node;
use Wikimedia\Parsoid\DOM\ProcessingInstruction;
use Wikimedia\Parsoid\DOM\Text;
/**
* ImmutableRange has a similar API to the DOM Range class.
*
* start/endContainer and offsets can be accessed, as can commonAncestorContainer
* which is lazy evaluated.
*
* setStart and setEnd are still available but return a cloned range.
*
* @property bool $collapsed
* @property Node $commonAncestorContainer
* @property Node $endContainer
* @property int $endOffset
* @property Node $startContainer
* @property int $startOffset
*/
class ImmutableRange {
private ?Node $mCommonAncestorContainer = null;
private Node $mEndContainer;
private int $mEndOffset;
private Node $mStartContainer;
private int $mStartOffset;
/**
* Find the common ancestor container of two nodes
*
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
* @param Node $a
* @param Node $b
* @return Node Common ancestor container
*/
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
private static function findCommonAncestorContainer( Node $a, Node $b ): Node {
$ancestorsA = [];
$ancestorsB = [];
$parent = $a;
do {
// While walking up the parents of $a we found $b is a parent of $a or even identical
if ( $parent === $b ) {
return $b;
}
$ancestorsA[] = $parent;
} while ( $parent = $parent->parentNode );
$parent = $b;
do {
// While walking up the parents of $b we found $a is a parent of $b or even identical
if ( $parent === $a ) {
return $a;
}
$ancestorsB[] = $parent;
} while ( $parent = $parent->parentNode );
$node = null;
// Start with the top-most (hopefully) identical root node, walk down, skip everything
// that's identical, and stop at the first mismatch
$indexA = count( $ancestorsA );
$indexB = count( $ancestorsB );
while ( $indexA-- && $indexB-- && $ancestorsA[$indexA] === $ancestorsB[$indexB] ) {
// Remember the last match closest to $a and $b
$node = $ancestorsA[$indexA];
}
if ( !$node ) {
throw new DOMException( 'Nodes are not in the same document' );
}
return $node;
}
/**
* Get the root ancestor of a node
*/
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
private static function getRootNode( Node $node ): Node {
while ( $node->parentNode ) {
$node = $node->parentNode;
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
'@phan-var Node $node';
}
return $node;
}
public function __construct(
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
Node $startNode, int $startOffset, Node $endNode, int $endOffset
) {
$this->mStartContainer = $startNode;
$this->mStartOffset = $startOffset;
$this->mEndContainer = $endNode;
$this->mEndOffset = $endOffset;
}
/**
* @param string $field Field name
* @return mixed
*/
public function __get( string $field ) {
switch ( $field ) {
case 'collapsed':
return $this->mStartContainer === $this->mEndContainer &&
$this->mStartOffset === $this->mEndOffset;
case 'commonAncestorContainer':
if ( !$this->mCommonAncestorContainer ) {
$this->mCommonAncestorContainer =
static::findCommonAncestorContainer( $this->mStartContainer, $this->mEndContainer );
}
return $this->mCommonAncestorContainer;
case 'endContainer':
return $this->mEndContainer;
case 'endOffset':
return $this->mEndOffset;
case 'startContainer':
return $this->mStartContainer;
case 'startOffset':
return $this->mStartOffset;
default:
throw new RuntimeException( 'Invalid property: ' . $field );
}
}
/**
* Clone range with a new start position
*/
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
public function setStart( Node $startNode, int $startOffset ): self {
return $this->setStartOrEnd( 'start', $startNode, $startOffset );
}
/**
* Clone range with a new end position
*/
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
public function setEnd( Node $endNode, int $endOffset ): self {
return $this->setStartOrEnd( 'end', $endNode, $endOffset );
}
/**
* Sets the start or end boundary point for the Range.
*
* Ported from https://github.com/TRowbotham/PHPDOM (MIT)
* @see https://dom.spec.whatwg.org/#concept-range-bp-set
*
* @param string $type Which boundary point should be set. Valid values are start or end.
* @param Node $node The Node that will become the boundary.
* @param int $offset The offset within the given Node that will be the boundary.
* @return self
*/
private function setStartOrEnd( string $type, Node $node, int $offset ): self {
if ( $node instanceof DocumentType ) {
throw new DOMException();
}
switch ( $type ) {
case 'start':
$endContainer = $this->mEndContainer;
$endOffset = $this->mEndOffset;
if (
self::getRootNode( $this->mStartContainer ) !== self::getRootNode( $node ) ||
$this->computePosition(
$node, $offset, $this->mEndContainer, $this->mEndOffset
) === 'after'
) {
$endContainer = $node;
$endOffset = $offset;
}
return new self(
$node, $offset, $endContainer, $endOffset
);
case 'end':
$startContainer = $this->mStartContainer;
$startOffset = $this->mStartOffset;
if (
self::getRootNode( $this->mStartContainer ) !== self::getRootNode( $node ) ||
$this->computePosition(
$node, $offset, $this->mStartContainer, $this->mStartOffset
) === 'before'
) {
$startContainer = $node;
$startOffset = $offset;
}
return new self(
$startContainer, $startOffset, $node, $offset
);
}
}
/**
* Returns true if only a portion of the Node is contained within the Range.
*
* Ported from https://github.com/TRowbotham/PHPDOM (MIT)
* @see https://dom.spec.whatwg.org/#partially-contained
*
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
* @param Node $node The Node to check against.
* @return bool
*/
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
private function isPartiallyContainedNode( Node $node ): bool {
return CommentUtils::contains( $node, $this->mStartContainer ) xor
CommentUtils::contains( $node, $this->mEndContainer );
}
/**
* Returns true if the entire Node is within the Range, otherwise false.
*
* Ported from https://github.com/TRowbotham/PHPDOM (MIT)
* @see https://dom.spec.whatwg.org/#contained
*
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
* @param Node $node The Node to check against.
* @return bool
*/
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
private function isFullyContainedNode( Node $node ): bool {
return static::getRootNode( $node ) === static::getRootNode( $this->mStartContainer )
&& $this->computePosition( $node, 0, $this->mStartContainer, $this->mStartOffset ) === 'after'
&& $this->computePosition(
// @phan-suppress-next-line PhanUndeclaredProperty
$node, $node->length ?? $node->childNodes->length,
$this->mEndContainer, $this->mEndOffset
) === 'before';
}
/**
* Extracts the content of the Range from the node tree and places it in a
* DocumentFragment.
*
* Ported from https://github.com/TRowbotham/PHPDOM (MIT)
* @see https://dom.spec.whatwg.org/#dom-range-extractcontents
*/
public function extractContents(): DocumentFragment {
$fragment = $this->mStartContainer->ownerDocument->createDocumentFragment();
if (
$this->mStartContainer === $this->mEndContainer
&& $this->mStartOffset === $this->mEndOffset
) {
return $fragment;
}
$originalStartNode = $this->mStartContainer;
$originalStartOffset = $this->mStartOffset;
$originalEndNode = $this->mEndContainer;
$originalEndOffset = $this->mEndOffset;
if (
$originalStartNode === $originalEndNode
&& ( $originalStartNode instanceof Text
|| $originalStartNode instanceof ProcessingInstruction
|| $originalStartNode instanceof Comment )
) {
$clone = $originalStartNode->cloneNode();
Assert::precondition( $clone instanceof CharacterData, 'TODO' );
$clone->data = $originalStartNode->substringData(
$originalStartOffset,
$originalEndOffset - $originalStartOffset
);
$fragment->appendChild( $clone );
$originalStartNode->replaceData(
$originalStartOffset,
$originalEndOffset - $originalStartOffset,
''
);
return $fragment;
}
$commonAncestor = $this->commonAncestorContainer;
// It should be impossible for common ancestor to be null here since both nodes should be
// in the same tree.
Assert::precondition( $commonAncestor !== null, 'TODO' );
$firstPartiallyContainedChild = null;
if ( !CommentUtils::contains( $originalStartNode, $originalEndNode ) ) {
foreach ( $commonAncestor->childNodes as $node ) {
if ( $this->isPartiallyContainedNode( $node ) ) {
$firstPartiallyContainedChild = $node;
break;
}
}
}
$lastPartiallyContainedChild = null;
if ( !CommentUtils::contains( $originalEndNode, $originalStartNode ) ) {
$node = $commonAncestor->lastChild;
while ( $node ) {
if ( $this->isPartiallyContainedNode( $node ) ) {
$lastPartiallyContainedChild = $node;
break;
}
$node = $node->previousSibling;
}
}
$containedChildren = [];
foreach ( $commonAncestor->childNodes as $childNode ) {
if ( $this->isFullyContainedNode( $childNode ) ) {
if ( $childNode instanceof DocumentType ) {
throw new DOMException();
}
$containedChildren[] = $childNode;
}
}
if ( CommentUtils::contains( $originalStartNode, $originalEndNode ) ) {
$newNode = $originalStartNode;
$newOffset = $originalStartOffset;
} else {
$referenceNode = $originalStartNode;
$parent = $referenceNode->parentNode;
while ( $parent && !CommentUtils::contains( $parent, $originalEndNode ) ) {
$referenceNode = $parent;
$parent = $referenceNode->parentNode;
}
// Note: If reference nodes parent is null, it would be the root of range, so would be an inclusive
// ancestor of original end node, and we could not reach this point.
Assert::precondition( $parent !== null, 'TODO' );
$newNode = $parent;
$newOffset = CommentUtils::childIndexOf( $referenceNode ) + 1;
}
if (
$firstPartiallyContainedChild instanceof Text
|| $firstPartiallyContainedChild instanceof ProcessingInstruction
|| $firstPartiallyContainedChild instanceof Comment
) {
// Note: In this case, first partially contained child is original start node.
Assert::precondition( $originalStartNode instanceof CharacterData, 'TODO' );
$clone = $originalStartNode->cloneNode();
Assert::precondition( $clone instanceof CharacterData, 'TODO' );
$clone->data = $originalStartNode->substringData(
$originalStartOffset,
$originalStartNode->length - $originalStartOffset
);
$fragment->appendChild( $clone );
$originalStartNode->replaceData(
$originalStartOffset,
$originalStartNode->length - $originalStartOffset,
''
);
} elseif ( $firstPartiallyContainedChild ) {
$clone = $firstPartiallyContainedChild->cloneNode();
$fragment->appendChild( $clone );
$subrange = clone $this;
$subrange->mStartContainer = $originalStartNode;
$subrange->mStartOffset = $originalStartOffset;
$subrange->mEndContainer = $firstPartiallyContainedChild;
$subrange->mEndOffset = count( $firstPartiallyContainedChild->childNodes );
$subfragment = $subrange->extractContents();
$clone->appendChild( $subfragment );
}
foreach ( $containedChildren as $child ) {
$fragment->appendChild( $child );
}
if (
$lastPartiallyContainedChild instanceof Text
|| $lastPartiallyContainedChild instanceof ProcessingInstruction
|| $lastPartiallyContainedChild instanceof Comment
) {
// Note: In this case, last partially contained child is original end node.
Assert::precondition( $originalEndNode instanceof CharacterData, 'TODO' );
$clone = $originalEndNode->cloneNode();
Assert::precondition( $clone instanceof CharacterData, 'TODO' );
$clone->data = $originalEndNode->substringData( 0, $originalEndOffset );
$fragment->appendChild( $clone );
$originalEndNode->replaceData( 0, $originalEndOffset, '' );
} elseif ( $lastPartiallyContainedChild ) {
$clone = $lastPartiallyContainedChild->cloneNode();
$fragment->appendChild( $clone );
$subrange = clone $this;
$subrange->mStartContainer = $lastPartiallyContainedChild;
$subrange->mStartOffset = 0;
$subrange->mEndContainer = $originalEndNode;
$subrange->mEndOffset = $originalEndOffset;
$subfragment = $subrange->extractContents();
$clone->appendChild( $subfragment );
}
$this->mStartContainer = $newNode;
$this->mStartOffset = $newOffset;
$this->mEndContainer = $newNode;
$this->mEndOffset = $newOffset;
return $fragment;
}
/**
* Ported from https://github.com/TRowbotham/PHPDOM (MIT)
* @see https://dom.spec.whatwg.org/#dom-range-clonecontents
*/
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
public function cloneContents(): DocumentFragment {
$ownerDocument = $this->mStartContainer->ownerDocument;
$fragment = $ownerDocument->createDocumentFragment();
if ( $this->mStartContainer === $this->mEndContainer
&& $this->mStartOffset === $this->mEndOffset
) {
return $fragment;
}
$originalStartContainer = $this->mStartContainer;
$originalStartOffset = $this->mStartOffset;
$originalEndContainer = $this->mEndContainer;
$originalEndOffset = $this->mEndOffset;
if ( $originalStartContainer === $originalEndContainer
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
&& ( $originalStartContainer instanceof Text
|| $originalStartContainer instanceof ProcessingInstruction
|| $originalStartContainer instanceof Comment )
) {
$clone = $originalStartContainer->cloneNode();
$clone->nodeValue = $originalStartContainer->substringData(
$originalStartOffset,
$originalEndOffset - $originalStartOffset
);
$fragment->appendChild( $clone );
return $fragment;
}
$commonAncestor = static::findCommonAncestorContainer(
$originalStartContainer,
$originalEndContainer
);
$firstPartiallyContainedChild = null;
if ( !CommentUtils::contains( $originalStartContainer, $originalEndContainer ) ) {
foreach ( $commonAncestor->childNodes as $node ) {
if ( $this->isPartiallyContainedNode( $node ) ) {
$firstPartiallyContainedChild = $node;
break;
}
}
}
$lastPartiallyContainedChild = null;
// Upstream uses lastChild then iterates over previousSibling, however this
// is much slower that copying all the nodes to an array, at least when using
// a native DOMNode, presumably because previousSibling is lazy-evaluated.
if ( !CommentUtils::contains( $originalEndContainer, $originalStartContainer ) ) {
$childNodes = iterator_to_array( $commonAncestor->childNodes );
foreach ( array_reverse( $childNodes ) as $node ) {
if ( $this->isPartiallyContainedNode( $node ) ) {
$lastPartiallyContainedChild = $node;
break;
}
}
}
$containedChildrenStart = null;
$containedChildrenEnd = null;
$child = $firstPartiallyContainedChild ?: $commonAncestor->firstChild;
for ( ; $child; $child = $child->nextSibling ) {
if ( $this->isFullyContainedNode( $child ) ) {
$containedChildrenStart = $child;
break;
}
}
$child = $lastPartiallyContainedChild ?: $commonAncestor->lastChild;
for ( ; $child !== $containedChildrenStart; $child = $child->previousSibling ) {
if ( $this->isFullyContainedNode( $child ) ) {
$containedChildrenEnd = $child;
break;
}
}
if ( !$containedChildrenEnd ) {
$containedChildrenEnd = $containedChildrenStart;
}
// $containedChildrenStart and $containedChildrenEnd may be null here, but this loop still works correctly
for ( $child = $containedChildrenStart; $child !== $containedChildrenEnd; $child = $child->nextSibling ) {
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
if ( $child instanceof DocumentType ) {
throw new DOMException();
}
}
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
if ( $firstPartiallyContainedChild instanceof Text
|| $firstPartiallyContainedChild instanceof ProcessingInstruction
|| $firstPartiallyContainedChild instanceof Comment
) {
$clone = $originalStartContainer->cloneNode();
Assert::precondition(
$firstPartiallyContainedChild === $originalStartContainer,
'Only possible when the node is the startContainer'
);
$clone->nodeValue = $firstPartiallyContainedChild->substringData(
$originalStartOffset,
$firstPartiallyContainedChild->length - $originalStartOffset
);
$fragment->appendChild( $clone );
} elseif ( $firstPartiallyContainedChild ) {
$clone = $firstPartiallyContainedChild->cloneNode();
$fragment->appendChild( $clone );
$subrange = new self(
$originalStartContainer, $originalStartOffset,
$firstPartiallyContainedChild,
// @phan-suppress-next-line PhanUndeclaredProperty
$firstPartiallyContainedChild->length ?? $firstPartiallyContainedChild->childNodes->length
);
$subfragment = $subrange->cloneContents();
if ( $subfragment->hasChildNodes() ) {
$clone->appendChild( $subfragment );
}
}
// $containedChildrenStart and $containedChildrenEnd may be null here, but this loop still works correctly
for ( $child = $containedChildrenStart; $child !== $containedChildrenEnd; $child = $child->nextSibling ) {
$clone = $child->cloneNode( true );
$fragment->appendChild( $clone );
}
// If not null, this node wasn't processed by the loop
if ( $containedChildrenEnd ) {
$clone = $containedChildrenEnd->cloneNode( true );
$fragment->appendChild( $clone );
}
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
if ( $lastPartiallyContainedChild instanceof Text
|| $lastPartiallyContainedChild instanceof ProcessingInstruction
|| $lastPartiallyContainedChild instanceof Comment
) {
Assert::precondition(
$lastPartiallyContainedChild === $originalEndContainer,
'Only possible when the node is the endContainer'
);
$clone = $lastPartiallyContainedChild->cloneNode();
$clone->nodeValue = $lastPartiallyContainedChild->substringData(
0,
$originalEndOffset
);
$fragment->appendChild( $clone );
} elseif ( $lastPartiallyContainedChild ) {
$clone = $lastPartiallyContainedChild->cloneNode();
$fragment->appendChild( $clone );
$subrange = new self(
$lastPartiallyContainedChild, 0,
$originalEndContainer, $originalEndOffset
);
$subfragment = $subrange->cloneContents();
if ( $subfragment->hasChildNodes() ) {
$clone->appendChild( $subfragment );
}
}
return $fragment;
}
/**
* Inserts a new Node into at the start of the Range.
*
* Ported from https://github.com/TRowbotham/PHPDOM (MIT)
*
* @see https://dom.spec.whatwg.org/#dom-range-insertnode
*
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
* @param Node $node The Node to be inserted.
* @return void
*/
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
public function insertNode( Node $node ): void {
if ( ( $this->mStartContainer instanceof ProcessingInstruction
|| $this->mStartContainer instanceof Comment )
|| ( $this->mStartContainer instanceof Text
&& $this->mStartContainer->parentNode === null )
) {
throw new DOMException();
}
$referenceNode = null;
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
if ( $this->mStartContainer instanceof Text ) {
$referenceNode = $this->mStartContainer;
} else {
$referenceNode = $this
->mStartContainer
->childNodes
->item( $this->mStartOffset );
}
$parent = !$referenceNode
? $this->mStartContainer
: $referenceNode->parentNode;
// TODO: Restore this validation check?
// $parent->ensurePreinsertionValidity( $node, $referenceNode );
Don't refer directly to PHP `dom` extension classes; avoid nonstandard behavior These changes ensure that DiscussionTools is independent of DOM library choice, and will not break if/when Parsoid switches to an alternate (more standards-compliant) DOM library. We run `phan` against the Dodo standards-compliant DOM library, so this ends up flagging uses of non-standard PHP extensions to the DOM. These will be suppressed for now with a "Nonstandard DOM" comment that can be grepped for, since they will eventually will need to be rewritten or worked around. Most frequent issues: * Node::nodeValue and Node::textContent and Element::getAttribute() can return null in a spec-compliant implementation. Add `?? ''` to make spec-compliant results consistent w/ what PHP returns. * DOMXPath doesn't accept anything except DOMDocument. These uses should be replaced with DOMCompat::querySelectorAll() or similar (which end up using DOMXPath under the covers for DOMDocument any way, but are implemented more efficiently in a spec-compliant implementation). * A couple of times we have code like: `while ($node->firstChild!==null) { $node = $node->firstChild; }` and phan's analysis isn't strong enough to determine that $node is still non-null after the while. This same issue should appear with DOMDocument but phan doesn't complain for some reason. One apparently legit issue: * Node::insertBefore() is once called in a funny way which leans on the fact that the second option is optional in PHP. This seems to be a workaround for an ancient PHP bug, and can probably be safely removed. Bug: T287611 Bug: T217867 Change-Id: I3c4f41c3819770f85d68157c9f690d650b7266a3
2021-07-29 02:16:15 +00:00
if ( $this->mStartContainer instanceof Text ) {
$referenceNode = $this->mStartContainer->splitText( $this->mStartOffset );
}
if ( $node === $referenceNode ) {
$referenceNode = $referenceNode->nextSibling;
}
if ( $node->parentNode ) {
$node->parentNode->removeChild( $node );
}
// TODO: Restore this validation check?
// $parent->preinsertNode( $node, $referenceNode );
// $referenceNode may be null, this is okay
$parent->insertBefore( $node, $referenceNode );
}
/**
* Wraps the content of Range in a new Node and inserts it in to the Document.
*
* Ported from https://github.com/TRowbotham/PHPDOM (MIT)
*
* @see https://dom.spec.whatwg.org/#dom-range-surroundcontents
*
* @param Node $newParent New parent node for contents
* @return void
*/
public function surroundContents( Node $newParent ): void {
$commonAncestor = $this->commonAncestorContainer;
if ( $commonAncestor ) {
$tw = new TreeWalker( $commonAncestor );
$node = $tw->nextNode();
while ( $node ) {
if ( !$node instanceof Text && $this->isPartiallyContainedNode( $node ) ) {
throw new DOMException();
}
$node = $tw->nextNode();
}
}
if (
$newParent instanceof Document
|| $newParent instanceof DocumentType
|| $newParent instanceof DocumentFragment
) {
throw new DOMException();
}
$fragment = $this->extractContents();
while ( $newParent->firstChild ) {
$newParent->removeChild( $newParent->firstChild );
}
$this->insertNode( $newParent );
$newParent->appendChild( $fragment );
// TODO: Return new range?
}
/**
* Compares the position of two boundary points.
*
* Ported from https://github.com/TRowbotham/PHPDOM (MIT)
* @internal
*
* @see https://dom.spec.whatwg.org/#concept-range-bp-position
*
* @param Node $nodeA
* @param int $offsetA
* @param Node $nodeB
* @param int $offsetB
* @return string 'before'|'after'|'equal'
*/
private function computePosition(
Node $nodeA, int $offsetA, Node $nodeB, int $offsetB
): string {
// 1. Assert: nodeA and nodeB have the same root.
// Removed, not necessary for our usage
// 2. If nodeA is nodeB, then return equal if offsetA is offsetB, before if offsetA is less than offsetB, and
// after if offsetA is greater than offsetB.
if ( $nodeA === $nodeB ) {
if ( $offsetA === $offsetB ) {
return 'equal';
} elseif ( $offsetA < $offsetB ) {
return 'before';
} else {
return 'after';
}
}
$commonAncestor = $this->findCommonAncestorContainer( $nodeB, $nodeA );
if ( $commonAncestor === $nodeA ) {
$AFollowsB = false;
} elseif ( $commonAncestor === $nodeB ) {
$AFollowsB = true;
} else {
// A was not found inside B. Traverse both A & B up to the nodes
// before their common ancestor, then see if A is in the nextSibling
// chain of B.
$b = $nodeB;
while ( $b->parentNode !== $commonAncestor ) {
$b = $b->parentNode;
}
$a = $nodeA;
while ( $a->parentNode !== $commonAncestor ) {
$a = $a->parentNode;
}
$AFollowsB = false;
while ( $b ) {
if ( $a === $b ) {
$AFollowsB = true;
break;
}
$b = $b->nextSibling;
}
}
if ( $AFollowsB ) {
// Swap variables
[ $nodeB, $nodeA ] = [ $nodeA, $nodeB ];
[ $offsetB, $offsetA ] = [ $offsetA, $offsetB ];
}
$ancestor = $nodeB->parentNode;
while ( $ancestor ) {
if ( $ancestor === $nodeA ) {
break;
}
$ancestor = $ancestor->parentNode;
}
if ( $ancestor ) {
$child = $nodeB;
while ( $child ) {
if ( $child->parentNode === $nodeA ) {
break;
}
$child = $child->parentNode;
}
// Phan complains that $child may be null here, but that can't happen, because at this point
// we know that $nodeA is an ancestor of $nodeB, so the loop above will stop before the root.
// @phan-suppress-next-line PhanTypeMismatchArgumentNullable
if ( CommentUtils::childIndexOf( $child ) < $offsetA ) {
return $AFollowsB ? 'before' : 'after';
}
}
return $AFollowsB ? 'after' : 'before';
}
public const START_TO_START = 0;
public const START_TO_END = 1;
public const END_TO_END = 2;
public const END_TO_START = 3;
/**
* Compares the boundary points of this Range with another Range.
*
* Ported from https://github.com/TRowbotham/PHPDOM (MIT)
*
* @see https://dom.spec.whatwg.org/#dom-range-compareboundarypoints
*
* @param int $how One of ImmutableRange::END_TO_END, ImmutableRange::END_TO_START,
* ImmutableRange::START_TO_END, ImmutableRange::START_TO_START
* @param ImmutableRange $sourceRange A Range whose boundary points are to be compared.
* @return int -1, 0, or 1
*/
public function compareBoundaryPoints( int $how, self $sourceRange ): int {
if ( static::getRootNode( $this->mStartContainer ) !== static::getRootNode( $sourceRange->startContainer ) ) {
throw new DOMException();
}
switch ( $how ) {
case static::START_TO_START:
$thisPoint = [ $this->mStartContainer, $this->mStartOffset ];
$otherPoint = [ $sourceRange->startContainer, $sourceRange->startOffset ];
break;
case static::START_TO_END:
$thisPoint = [ $this->mEndContainer, $this->mEndOffset ];
$otherPoint = [ $sourceRange->startContainer, $sourceRange->startOffset ];
break;
case static::END_TO_END:
$thisPoint = [ $this->mEndContainer, $this->mEndOffset ];
$otherPoint = [ $sourceRange->endContainer, $sourceRange->endOffset ];
break;
case static::END_TO_START:
$thisPoint = [ $this->mStartContainer, $this->mStartOffset ];
$otherPoint = [ $sourceRange->endContainer, $sourceRange->endOffset ];
break;
default:
throw new DOMException();
}
switch ( $this->computePosition( ...$thisPoint, ...$otherPoint ) ) {
case 'before':
return -1;
case 'equal':
return 0;
case 'after':
return 1;
default:
throw new DOMException();
}
}
}