mediawiki-extensions-Discus.../includes/ThreadItem/ContentCommentItem.php

236 lines
6.8 KiB
PHP

<?php
namespace MediaWiki\Extension\DiscussionTools\ThreadItem;
use DateTimeImmutable;
use MediaWiki\Extension\DiscussionTools\CommentModifier;
use MediaWiki\Extension\DiscussionTools\CommentUtils;
use MediaWiki\Extension\DiscussionTools\ImmutableRange;
use MediaWiki\MediaWikiServices;
use MediaWiki\Parser\Sanitizer;
use MediaWiki\Title\Title;
use Wikimedia\Parsoid\DOM\DocumentFragment;
use Wikimedia\Parsoid\DOM\Text;
use Wikimedia\Parsoid\Utils\DOMCompat;
use Wikimedia\Parsoid\Utils\DOMUtils;
class ContentCommentItem extends ContentThreadItem implements CommentItem {
use CommentItemTrait {
getHeading as protected traitGetHeading;
getSubscribableHeading as protected traitGetSubscribableHeading;
jsonSerialize as protected traitJsonSerialize;
}
/** @var ImmutableRange[] */
private array $signatureRanges;
/** @var ImmutableRange[] */
private array $timestampRanges;
private DateTimeImmutable $timestamp;
private string $author;
private ?string $displayName;
/**
* @param int $level
* @param ImmutableRange $range
* @param bool|string $transcludedFrom
* @param ImmutableRange[] $signatureRanges Objects describing the extent of signatures (plus
* timestamps) for this comment. There is always at least one signature, but there may be
* multiple. The author and timestamp of the comment is determined from the first signature.
* @param ImmutableRange[] $timestampRanges Objects describing the extent of timestamps within
* the above signatures.
* @param DateTimeImmutable $timestamp
* @param string $author Comment author's username
* @param ?string $displayName Comment author's display name
*/
public function __construct(
int $level, ImmutableRange $range, $transcludedFrom,
array $signatureRanges, array $timestampRanges,
DateTimeImmutable $timestamp,
string $author, ?string $displayName = null
) {
parent::__construct( 'comment', $level, $range, $transcludedFrom );
$this->signatureRanges = $signatureRanges;
$this->timestampRanges = $timestampRanges;
$this->timestamp = $timestamp;
$this->author = $author;
$this->displayName = $displayName;
}
/**
* @inheritDoc CommentItemTrait::jsonSerialize
*/
public function jsonSerialize( bool $deep = false, ?callable $callback = null ): array {
$data = $this->traitJsonSerialize( $deep, $callback );
if ( $this->displayName ) {
$data['displayName'] = $this->displayName;
}
return $data;
}
/**
* Get the DOM of this comment's body
*
* @param bool $stripTrailingSeparator Strip a trailing separator between the body and
* the signature which consists of whitespace and hyphens e.g. ' --'
* @return DocumentFragment Cloned fragment of the body content
*/
private function getBodyFragment( bool $stripTrailingSeparator = false ): DocumentFragment {
$fragment = $this->getBodyRange()->cloneContents();
CommentModifier::unwrapFragment( $fragment );
if ( $stripTrailingSeparator ) {
// Find a trailing text node
$lastChild = $fragment->lastChild;
while (
$lastChild &&
!( $lastChild instanceof Text )
) {
$lastChild = $lastChild->lastChild;
}
if (
$lastChild instanceof Text &&
preg_match( '/[\s\-~\x{2010}-\x{2015}\x{2043}\x{2060}]+$/u', $lastChild->nodeValue ?? '', $matches )
) {
$lastChild->nodeValue =
substr( $lastChild->nodeValue ?? '', 0, -strlen( $matches[0] ) );
}
}
return $fragment;
}
/**
* Get the HTML of this comment's body
*
*
* @param bool $stripTrailingSeparator See getBodyFragment
* @return string HTML
*/
public function getBodyHTML( bool $stripTrailingSeparator = false ): string {
$fragment = $this->getBodyFragment( $stripTrailingSeparator );
return DOMUtils::getFragmentInnerHTML( $fragment );
}
/**
* Get the text of this comment's body
*
* @param bool $stripTrailingSeparator See getBodyFragment
* @return string Text
*/
public function getBodyText( bool $stripTrailingSeparator = false ): string {
$html = $this->getBodyHTML( $stripTrailingSeparator );
return Sanitizer::stripAllTags( $html );
}
/**
* Get a list of all users mentioned
*
* @return Title[] Title objects for mentioned user pages
*/
public function getMentions(): array {
$fragment = $this->getBodyRange()->cloneContents();
// Note: DOMCompat::getElementsByTagName() doesn't take a DocumentFragment argument
$links = DOMCompat::querySelectorAll( $fragment, 'a' );
$users = [];
foreach ( $links as $link ) {
$href = $link->getAttribute( 'href' );
if ( $href ) {
$siteConfig = MediaWikiServices::getInstance()->getMainConfig();
$title = Title::newFromText( CommentUtils::getTitleFromUrl( $href, $siteConfig ) );
if ( $title && $title->inNamespace( NS_USER ) ) {
// TODO: Consider returning User objects
$users[] = $title;
}
}
}
return array_unique( $users );
}
/**
* @return ImmutableRange[] Comment signature ranges
*/
public function getSignatureRanges(): array {
return $this->signatureRanges;
}
/**
* @return ImmutableRange[] Comment timestamp ranges
*/
public function getTimestampRanges(): array {
return $this->timestampRanges;
}
/**
* @return ImmutableRange Range of the comment's "body"
*/
public function getBodyRange(): ImmutableRange {
// Exclude last signature from body
$signatureRanges = $this->getSignatureRanges();
$lastSignature = end( $signatureRanges );
return $this->getRange()->setEnd( $lastSignature->startContainer, $lastSignature->startOffset );
}
/**
* @return DateTimeImmutable Comment timestamp
*/
public function getTimestamp(): DateTimeImmutable {
return $this->timestamp;
}
/**
* @return string Comment author
*/
public function getAuthor(): string {
return $this->author;
}
/**
* @return ?string Comment display name
*/
public function getDisplayName(): ?string {
return $this->displayName;
}
/**
* @inheritDoc CommentItemTrait::getHeading
* @suppress PhanTypeMismatchReturnSuperType
*/
public function getHeading(): ContentHeadingItem {
return $this->traitGetHeading();
}
/**
* @inheritDoc CommentItemTrait::getSubscribableHeading
*/
public function getSubscribableHeading(): ?ContentHeadingItem {
return $this->traitGetSubscribableHeading();
}
/**
* @param ImmutableRange $signatureRange Comment signature range to add
*/
public function addSignatureRange( ImmutableRange $signatureRange ): void {
$this->signatureRanges[] = $signatureRange;
}
/**
* @param ImmutableRange[] $signatureRanges Comment signature ranges
*/
public function setSignatureRanges( array $signatureRanges ): void {
$this->signatureRanges = $signatureRanges;
}
/**
* @param DateTimeImmutable $timestamp Comment timestamp
*/
public function setTimestamp( DateTimeImmutable $timestamp ): void {
$this->timestamp = $timestamp;
}
/**
* @param string $author Comment author
*/
public function setAuthor( string $author ): void {
$this->author = $author;
}
}