2020-05-22 16:26:05 +00:00
|
|
|
<?php
|
|
|
|
|
2022-03-18 03:28:06 +00:00
|
|
|
namespace MediaWiki\Extension\DiscussionTools\ThreadItem;
|
2020-05-22 16:26:05 +00:00
|
|
|
|
2021-11-08 17:47:03 +00:00
|
|
|
use DateTimeImmutable;
|
2022-03-18 03:28:06 +00:00
|
|
|
use MediaWiki\Extension\DiscussionTools\CommentModifier;
|
|
|
|
use MediaWiki\Extension\DiscussionTools\CommentUtils;
|
|
|
|
use MediaWiki\Extension\DiscussionTools\ImmutableRange;
|
2022-02-21 17:29:50 +00:00
|
|
|
use MediaWiki\MediaWikiServices;
|
2023-08-19 18:16:15 +00:00
|
|
|
use MediaWiki\Title\Title;
|
2022-02-10 20:34:05 +00:00
|
|
|
use Sanitizer;
|
2021-07-29 02:16:15 +00:00
|
|
|
use Wikimedia\Parsoid\DOM\DocumentFragment;
|
|
|
|
use Wikimedia\Parsoid\DOM\Text;
|
2020-07-22 18:25:34 +00:00
|
|
|
use Wikimedia\Parsoid\Utils\DOMCompat;
|
2022-02-08 22:11:24 +00:00
|
|
|
use Wikimedia\Parsoid\Utils\DOMUtils;
|
2020-06-29 13:30:47 +00:00
|
|
|
|
2022-03-18 03:28:06 +00:00
|
|
|
class ContentCommentItem extends ContentThreadItem implements CommentItem {
|
|
|
|
use CommentItemTrait {
|
|
|
|
getHeading as protected traitGetHeading;
|
|
|
|
getSubscribableHeading as protected traitGetSubscribableHeading;
|
2022-02-04 18:16:24 +00:00
|
|
|
jsonSerialize as protected traitJsonSerialize;
|
2022-03-18 03:28:06 +00:00
|
|
|
}
|
|
|
|
|
2023-09-16 00:31:47 +00:00
|
|
|
/** @var ImmutableRange[] */
|
|
|
|
private array $signatureRanges;
|
|
|
|
/** @var ImmutableRange[] */
|
|
|
|
private array $timestampRanges;
|
|
|
|
private DateTimeImmutable $timestamp;
|
|
|
|
private string $author;
|
|
|
|
private ?string $displayName;
|
2020-05-22 16:26:05 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $level
|
|
|
|
* @param ImmutableRange $range
|
2020-06-25 12:23:17 +00:00
|
|
|
* @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.
|
2022-10-04 12:50:57 +00:00
|
|
|
* @param ImmutableRange[] $timestampRanges Objects describing the extent of timestamps within
|
|
|
|
* the above signatures.
|
2021-11-08 17:47:03 +00:00
|
|
|
* @param DateTimeImmutable $timestamp
|
2021-01-08 19:44:15 +00:00
|
|
|
* @param string $author Comment author's username
|
2022-02-04 18:16:24 +00:00
|
|
|
* @param ?string $displayName Comment author's display name
|
2020-05-22 16:26:05 +00:00
|
|
|
*/
|
|
|
|
public function __construct(
|
|
|
|
int $level, ImmutableRange $range,
|
2022-10-04 12:50:57 +00:00
|
|
|
array $signatureRanges, array $timestampRanges,
|
|
|
|
DateTimeImmutable $timestamp,
|
2022-02-04 18:16:24 +00:00
|
|
|
string $author, ?string $displayName = null
|
2020-05-22 16:26:05 +00:00
|
|
|
) {
|
|
|
|
parent::__construct( 'comment', $level, $range );
|
|
|
|
$this->signatureRanges = $signatureRanges;
|
2022-10-04 12:50:57 +00:00
|
|
|
$this->timestampRanges = $timestampRanges;
|
2020-05-22 16:26:05 +00:00
|
|
|
$this->timestamp = $timestamp;
|
|
|
|
$this->author = $author;
|
2022-02-04 18:16:24 +00:00
|
|
|
$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;
|
2020-05-22 16:26:05 +00:00
|
|
|
}
|
|
|
|
|
2020-07-22 18:25:34 +00:00
|
|
|
/**
|
|
|
|
* Get the HTML of this comment's body
|
|
|
|
*
|
2020-11-20 20:09:55 +00:00
|
|
|
* @param bool $stripTrailingSeparator Strip a trailing separator between the body and
|
|
|
|
* the signature which consists of whitespace and hyphens e.g. ' --'
|
2021-07-29 02:16:15 +00:00
|
|
|
* @return DocumentFragment Cloned fragment of the body content
|
2020-07-22 18:25:34 +00:00
|
|
|
*/
|
2021-07-29 02:16:15 +00:00
|
|
|
private function getBodyFragment( bool $stripTrailingSeparator = false ): DocumentFragment {
|
2020-07-22 18:25:34 +00:00
|
|
|
$fragment = $this->getBodyRange()->cloneContents();
|
2020-11-20 00:21:30 +00:00
|
|
|
CommentModifier::unwrapFragment( $fragment );
|
2020-11-20 20:09:55 +00:00
|
|
|
|
|
|
|
if ( $stripTrailingSeparator ) {
|
|
|
|
// Find a trailing text node
|
|
|
|
$lastChild = $fragment->lastChild;
|
|
|
|
while (
|
2022-03-03 16:14:25 +00:00
|
|
|
$lastChild &&
|
|
|
|
!( $lastChild instanceof Text )
|
2020-11-20 20:09:55 +00:00
|
|
|
) {
|
|
|
|
$lastChild = $lastChild->lastChild;
|
|
|
|
}
|
|
|
|
if (
|
2021-07-29 02:16:15 +00:00
|
|
|
$lastChild instanceof Text &&
|
|
|
|
preg_match( '/[\s\-~\x{2010}-\x{2015}\x{2043}\x{2060}]+$/u', $lastChild->nodeValue ?? '', $matches )
|
2020-11-20 20:09:55 +00:00
|
|
|
) {
|
|
|
|
$lastChild->nodeValue =
|
2021-07-29 02:16:15 +00:00
|
|
|
substr( $lastChild->nodeValue ?? '', 0, -strlen( $matches[0] ) );
|
2020-11-20 20:09:55 +00:00
|
|
|
}
|
|
|
|
}
|
2021-02-23 22:50:22 +00:00
|
|
|
return $fragment;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the HTML of this comment's body
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @param bool $stripTrailingSeparator See getBodyFragment
|
|
|
|
* @return string HTML
|
|
|
|
*/
|
2021-07-22 07:25:13 +00:00
|
|
|
public function getBodyHTML( bool $stripTrailingSeparator = false ): string {
|
2021-02-23 22:50:22 +00:00
|
|
|
$fragment = $this->getBodyFragment( $stripTrailingSeparator );
|
2022-02-08 22:11:24 +00:00
|
|
|
return DOMUtils::getFragmentInnerHTML( $fragment );
|
2020-07-22 18:25:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the text of this comment's body
|
|
|
|
*
|
2021-02-23 22:50:22 +00:00
|
|
|
* @param bool $stripTrailingSeparator See getBodyFragment
|
2020-07-22 18:25:34 +00:00
|
|
|
* @return string Text
|
|
|
|
*/
|
2021-07-22 07:25:13 +00:00
|
|
|
public function getBodyText( bool $stripTrailingSeparator = false ): string {
|
2022-02-10 20:34:05 +00:00
|
|
|
$html = $this->getBodyHTML( $stripTrailingSeparator );
|
|
|
|
return Sanitizer::stripAllTags( $html );
|
2020-07-22 18:25:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a list of all users mentioned
|
|
|
|
*
|
|
|
|
* @return Title[] Title objects for mentioned user pages
|
|
|
|
*/
|
2021-07-22 07:25:13 +00:00
|
|
|
public function getMentions(): array {
|
2020-07-22 18:25:34 +00:00
|
|
|
$fragment = $this->getBodyRange()->cloneContents();
|
2021-08-02 13:07:41 +00:00
|
|
|
// Note: DOMCompat::getElementsByTagName() doesn't take a DocumentFragment argument
|
|
|
|
$links = DOMCompat::querySelectorAll( $fragment, 'a' );
|
2020-07-22 18:25:34 +00:00
|
|
|
$users = [];
|
|
|
|
foreach ( $links as $link ) {
|
2021-08-02 13:07:41 +00:00
|
|
|
$href = $link->getAttribute( 'href' );
|
|
|
|
if ( $href ) {
|
2022-02-21 17:29:50 +00:00
|
|
|
$siteConfig = MediaWikiServices::getInstance()->getMainConfig();
|
2022-02-21 22:07:38 +00:00
|
|
|
$title = Title::newFromText( CommentUtils::getTitleFromUrl( $href, $siteConfig ) );
|
2023-11-28 08:49:00 +00:00
|
|
|
if ( $title && $title->inNamespace( NS_USER ) ) {
|
2021-08-02 13:07:41 +00:00
|
|
|
// TODO: Consider returning User objects
|
|
|
|
$users[] = $title;
|
|
|
|
}
|
2020-07-22 18:25:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return array_unique( $users );
|
|
|
|
}
|
|
|
|
|
2020-05-22 16:26:05 +00:00
|
|
|
/**
|
|
|
|
* @return ImmutableRange[] Comment signature ranges
|
|
|
|
*/
|
2021-07-22 07:25:13 +00:00
|
|
|
public function getSignatureRanges(): array {
|
2020-05-22 16:26:05 +00:00
|
|
|
return $this->signatureRanges;
|
|
|
|
}
|
|
|
|
|
2022-10-04 12:50:57 +00:00
|
|
|
/**
|
|
|
|
* @return ImmutableRange[] Comment timestamp ranges
|
|
|
|
*/
|
|
|
|
public function getTimestampRanges(): array {
|
|
|
|
return $this->timestampRanges;
|
|
|
|
}
|
|
|
|
|
2020-07-22 18:25:34 +00:00
|
|
|
/**
|
|
|
|
* @return ImmutableRange Range of the thread item's "body"
|
|
|
|
*/
|
2021-07-22 07:25:13 +00:00
|
|
|
public function getBodyRange(): ImmutableRange {
|
2020-07-22 18:25:34 +00:00
|
|
|
// Exclude last signature from body
|
|
|
|
$signatureRanges = $this->getSignatureRanges();
|
|
|
|
$lastSignature = end( $signatureRanges );
|
|
|
|
return $this->getRange()->setEnd( $lastSignature->startContainer, $lastSignature->startOffset );
|
|
|
|
}
|
|
|
|
|
2020-05-22 16:26:05 +00:00
|
|
|
/**
|
2021-11-08 17:47:03 +00:00
|
|
|
* @return DateTimeImmutable Comment timestamp
|
2020-05-22 16:26:05 +00:00
|
|
|
*/
|
2021-11-08 17:47:03 +00:00
|
|
|
public function getTimestamp(): DateTimeImmutable {
|
2020-05-22 16:26:05 +00:00
|
|
|
return $this->timestamp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-02-22 20:48:01 +00:00
|
|
|
* @return string Comment author
|
2020-05-22 16:26:05 +00:00
|
|
|
*/
|
2021-07-22 07:25:13 +00:00
|
|
|
public function getAuthor(): string {
|
2020-05-22 16:26:05 +00:00
|
|
|
return $this->author;
|
|
|
|
}
|
|
|
|
|
2022-02-04 18:16:24 +00:00
|
|
|
/**
|
|
|
|
* @return ?string Comment display name
|
|
|
|
*/
|
|
|
|
public function getDisplayName(): ?string {
|
|
|
|
return $this->displayName;
|
|
|
|
}
|
|
|
|
|
2020-06-29 13:30:47 +00:00
|
|
|
/**
|
2022-03-18 03:28:06 +00:00
|
|
|
* @inheritDoc CommentItemTrait::getHeading
|
|
|
|
* @suppress PhanTypeMismatchReturnSuperType
|
2020-06-29 13:30:47 +00:00
|
|
|
*/
|
2022-03-18 03:28:06 +00:00
|
|
|
public function getHeading(): ContentHeadingItem {
|
|
|
|
return $this->traitGetHeading();
|
2020-06-29 13:30:47 +00:00
|
|
|
}
|
|
|
|
|
2021-09-01 22:05:00 +00:00
|
|
|
/**
|
2022-03-18 03:28:06 +00:00
|
|
|
* @inheritDoc CommentItemTrait::getSubscribableHeading
|
2021-09-01 22:05:00 +00:00
|
|
|
*/
|
2022-03-18 03:28:06 +00:00
|
|
|
public function getSubscribableHeading(): ?ContentHeadingItem {
|
|
|
|
return $this->traitGetSubscribableHeading();
|
2021-09-01 22:05:00 +00:00
|
|
|
}
|
|
|
|
|
2020-05-22 16:26:05 +00:00
|
|
|
/**
|
|
|
|
* @param ImmutableRange $signatureRange Comment signature range to add
|
|
|
|
*/
|
2021-07-22 07:25:13 +00:00
|
|
|
public function addSignatureRange( ImmutableRange $signatureRange ): void {
|
2020-05-22 16:26:05 +00:00
|
|
|
$this->signatureRanges[] = $signatureRange;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param ImmutableRange[] $signatureRanges Comment signature ranges
|
|
|
|
*/
|
2021-07-22 07:25:13 +00:00
|
|
|
public function setSignatureRanges( array $signatureRanges ): void {
|
2020-05-22 16:26:05 +00:00
|
|
|
$this->signatureRanges = $signatureRanges;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-11-08 17:47:03 +00:00
|
|
|
* @param DateTimeImmutable $timestamp Comment timestamp
|
2020-05-22 16:26:05 +00:00
|
|
|
*/
|
2021-11-08 17:47:03 +00:00
|
|
|
public function setTimestamp( DateTimeImmutable $timestamp ): void {
|
2020-05-22 16:26:05 +00:00
|
|
|
$this->timestamp = $timestamp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-02-22 20:48:01 +00:00
|
|
|
* @param string $author Comment author
|
2020-05-22 16:26:05 +00:00
|
|
|
*/
|
2021-07-22 07:25:13 +00:00
|
|
|
public function setAuthor( string $author ): void {
|
2020-05-22 16:26:05 +00:00
|
|
|
$this->author = $author;
|
|
|
|
}
|
|
|
|
}
|