type = $type; $this->level = $level; $this->range = $range; $this->transcludedFrom = $transcludedFrom; } /** * Get summary metadata for a thread. */ private function calculateThreadSummary(): void { if ( $this->authors !== null ) { return; } $authors = []; $commentCount = 0; $oldestReply = null; $latestReply = null; $threadScan = static function ( ContentThreadItem $comment ) use ( &$authors, &$commentCount, &$oldestReply, &$latestReply, &$threadScan ) { if ( $comment instanceof ContentCommentItem ) { $author = $comment->getAuthor(); if ( !isset( $authors[ $author] ) ) { $authors[ $author ] = [ 'username' => $author, 'displayNames' => [], ]; } $displayName = $comment->getDisplayName(); if ( $displayName && !in_array( $displayName, $authors[ $author ][ 'displayNames' ], true ) ) { $authors[ $author ][ 'displayNames' ][] = $displayName; } if ( !$oldestReply || ( $comment->getTimestamp() < $oldestReply->getTimestamp() ) ) { $oldestReply = $comment; } if ( !$latestReply || ( $latestReply->getTimestamp() < $comment->getTimestamp() ) ) { $latestReply = $comment; } $commentCount++; } // Get the set of authors in the same format from each reply $replies = $comment->getReplies(); array_walk( $replies, $threadScan ); }; $replies = $this->getReplies(); array_walk( $replies, $threadScan ); ksort( $authors ); $this->authors = array_values( $authors ); $this->commentCount = $commentCount; $this->oldestReply = $oldestReply; $this->latestReply = $latestReply; } /** * Get the list of authors in the tree below this thread item. * * Usually called on a HeadingItem to find all authors in a thread. * * @return array[] Authors, with `username` and `displayNames` (list of display names) properties. */ public function getAuthorsBelow(): array { $this->calculateThreadSummary(); return $this->authors; } /** * Get the number of comment items in the tree below this thread item. */ public function getCommentCount(): int { $this->calculateThreadSummary(); return $this->commentCount; } /** * Get the latest reply in the tree below this thread item, null if there are no replies */ public function getLatestReply(): ?ContentCommentItem { $this->calculateThreadSummary(); return $this->latestReply; } /** * Get the oldest reply in the tree below this thread item, null if there are no replies */ public function getOldestReply(): ?ContentCommentItem { $this->calculateThreadSummary(); return $this->oldestReply; } /** * Get a flat list of thread items in the comment tree below this thread item. * * @return ContentThreadItem[] Thread items */ public function getThreadItemsBelow(): array { $threadItems = []; $getReplies = static function ( ContentThreadItem $threadItem ) use ( &$threadItems, &$getReplies ) { $threadItems[] = $threadItem; foreach ( $threadItem->getReplies() as $reply ) { $getReplies( $reply ); } }; foreach ( $this->getReplies() as $reply ) { $getReplies( $reply ); } return $threadItems; } /** * @inheritDoc */ public function getTranscludedFrom() { return $this->transcludedFrom; } /** * Get the HTML of this thread item * * @return string HTML */ public function getHTML(): string { $fragment = $this->getRange()->cloneContents(); CommentModifier::unwrapFragment( $fragment ); $editsection = DOMCompat::querySelector( $fragment, 'mw\\:editsection' ); if ( $editsection ) { $editsection->parentNode->removeChild( $editsection ); } return DOMUtils::getFragmentInnerHTML( $fragment ); } /** * Get the text of this thread item * * @return string Text */ public function getText(): string { $html = $this->getHTML(); return Sanitizer::stripAllTags( $html ); } /** * @return string Thread item type */ public function getType(): string { return $this->type; } /** * @return int Indentation level */ public function getLevel(): int { return $this->level; } /** * @return ContentThreadItem|null Parent thread item */ public function getParent(): ?ThreadItem { return $this->parent; } /** * @return ImmutableRange Range of the entire thread item */ public function getRange(): ImmutableRange { return $this->range; } /** * @return Element Root node (level is relative to this node) */ public function getRootNode(): Element { return $this->rootNode; } /** * @return string Thread item name */ public function getName(): string { return $this->name; } /** * @return string Thread ID */ public function getId(): string { return $this->id; } /** * @return string|null Thread ID, before most recent change to ID calculation */ public function getLegacyId(): ?string { return $this->legacyId; } /** * @return ContentThreadItem[] Replies to this thread item */ public function getReplies(): array { return $this->replies; } /** * @return string[] Warnings */ public function getWarnings(): array { return $this->warnings; } /** * @param int $level Indentation level */ public function setLevel( int $level ): void { $this->level = $level; } public function setParent( ContentThreadItem $parent ): void { $this->parent = $parent; } /** * @param ImmutableRange $range Thread item range */ public function setRange( ImmutableRange $range ): void { $this->range = $range; } /** * @param Element $rootNode Root node (level is relative to this node) */ public function setRootNode( Element $rootNode ): void { $this->rootNode = $rootNode; } /** * @param string $name Thread item name */ public function setName( string $name ): void { $this->name = $name; } /** * @param string $id Thread ID */ public function setId( string $id ): void { $this->id = $id; } /** * @param string|null $legacyId Thread ID */ public function setLegacyId( ?string $legacyId ): void { $this->legacyId = $legacyId; } public function addWarning( string $warning ): void { $this->warnings[] = $warning; } /** * @param string[] $warnings */ public function addWarnings( array $warnings ): void { $this->warnings = array_merge( $this->warnings, $warnings ); } /** * @param ContentThreadItem $reply Reply comment */ public function addReply( ContentThreadItem $reply ): void { $this->replies[] = $reply; } }