2022-02-04 23:21:29 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace MediaWiki\Extension\DiscussionTools;
|
|
|
|
|
|
|
|
use ApiBase;
|
|
|
|
use ApiMain;
|
2022-03-29 19:29:46 +00:00
|
|
|
use MediaWiki\Extension\VisualEditor\ApiParsoidTrait;
|
2022-02-04 23:21:29 +00:00
|
|
|
use Title;
|
|
|
|
use Wikimedia\ParamValidator\ParamValidator;
|
2022-03-28 21:39:13 +00:00
|
|
|
use Wikimedia\Parsoid\Utils\DOMUtils;
|
2022-02-04 23:21:29 +00:00
|
|
|
|
|
|
|
class ApiDiscussionToolsPageInfo extends ApiBase {
|
|
|
|
|
2022-01-31 15:01:32 +00:00
|
|
|
use ApiDiscussionToolsTrait;
|
2022-02-04 23:21:29 +00:00
|
|
|
use ApiParsoidTrait;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
public function __construct( ApiMain $main, string $name ) {
|
|
|
|
parent::__construct( $main, $name );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
public function execute() {
|
|
|
|
$params = $this->extractRequestParams();
|
|
|
|
$title = Title::newFromText( $params['page'] );
|
2022-03-21 21:14:47 +00:00
|
|
|
$prop = array_fill_keys( $params['prop'], true );
|
2022-02-04 23:21:29 +00:00
|
|
|
|
|
|
|
if ( !$title ) {
|
|
|
|
$this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['page'] ) ] );
|
|
|
|
}
|
|
|
|
|
2022-01-31 15:01:32 +00:00
|
|
|
$revision = $this->getValidRevision( $title, $params['oldid'] ?? null );
|
2022-02-19 06:31:34 +00:00
|
|
|
$threadItemSet = $this->parseRevision( $revision );
|
2022-02-04 23:21:29 +00:00
|
|
|
|
2022-03-21 21:14:47 +00:00
|
|
|
$result = [];
|
2022-02-04 23:21:29 +00:00
|
|
|
|
2022-03-21 21:14:47 +00:00
|
|
|
if ( isset( $prop['transcludedfrom'] ) ) {
|
|
|
|
$threadItems = $threadItemSet->getThreadItems();
|
|
|
|
$transcludedFrom = [];
|
|
|
|
foreach ( $threadItems as $threadItem ) {
|
|
|
|
$from = $threadItem->getTranscludedFrom();
|
2022-02-04 23:21:29 +00:00
|
|
|
|
2022-03-21 21:14:47 +00:00
|
|
|
// Key by IDs, legacy IDs, and names. This assumes that they can never conflict.
|
2022-02-04 23:21:29 +00:00
|
|
|
|
2022-03-21 21:14:47 +00:00
|
|
|
$transcludedFrom[ $threadItem->getId() ] = $from;
|
|
|
|
|
|
|
|
$legacyId = $threadItem->getLegacyId();
|
|
|
|
if ( $legacyId ) {
|
|
|
|
$transcludedFrom[ $legacyId ] = $from;
|
|
|
|
}
|
2022-02-04 23:21:29 +00:00
|
|
|
|
2022-03-21 21:14:47 +00:00
|
|
|
$name = $threadItem->getName();
|
|
|
|
if ( isset( $transcludedFrom[ $name ] ) && $transcludedFrom[ $name ] !== $from ) {
|
|
|
|
// Two or more items with the same name, transcluded from different pages.
|
|
|
|
// Consider them both to be transcluded from unknown source.
|
|
|
|
$transcludedFrom[ $name ] = true;
|
|
|
|
} else {
|
|
|
|
$transcludedFrom[ $name ] = $from;
|
|
|
|
}
|
2022-02-04 23:21:29 +00:00
|
|
|
}
|
2022-03-21 21:14:47 +00:00
|
|
|
|
|
|
|
$result['transcludedfrom'] = $transcludedFrom;
|
2022-02-04 23:21:29 +00:00
|
|
|
}
|
|
|
|
|
2022-03-21 21:14:47 +00:00
|
|
|
if ( isset( $prop['threaditemshtml'] ) ) {
|
|
|
|
$threads = $threadItemSet->getThreads();
|
2022-05-17 00:36:05 +00:00
|
|
|
if ( count( $threads ) > 0 ) {
|
|
|
|
$firstHeading = $threads[0];
|
|
|
|
if ( !$firstHeading->isPlaceholderHeading() ) {
|
|
|
|
$range = new ImmutableRange( $firstHeading->getRootNode(), 0, $firstHeading->getRootNode(), 0 );
|
|
|
|
$fakeHeading = new HeadingItem( $range, 99, true );
|
|
|
|
$fakeHeading->setRootNode( $firstHeading->getRootNode() );
|
|
|
|
array_unshift( $threads, $fakeHeading );
|
|
|
|
}
|
|
|
|
}
|
2022-03-28 21:39:13 +00:00
|
|
|
$output = array_map( static function ( ThreadItem $item ) {
|
2022-03-21 21:14:47 +00:00
|
|
|
return $item->jsonSerialize( true, static function ( array &$array, ThreadItem $item ) {
|
|
|
|
$array['html'] = $item->getHtml();
|
|
|
|
} );
|
|
|
|
}, $threads );
|
2022-03-28 21:39:13 +00:00
|
|
|
foreach ( $threads as $index => $item ) {
|
|
|
|
// need to loop over this to fix up empty sections, because we
|
|
|
|
// need context that's not available inside the array map
|
|
|
|
if ( $item instanceof HeadingItem && count( $item->getReplies() ) === 0 ) {
|
|
|
|
$nextItem = $threads[ $index + 1 ] ?? false;
|
|
|
|
$startRange = $item->getRange();
|
|
|
|
if ( $nextItem ) {
|
|
|
|
$nextRange = $nextItem->getRange();
|
|
|
|
$nextStart = $nextRange->startContainer->previousSibling ?: $nextRange->startContainer;
|
|
|
|
$betweenRange = new ImmutableRange(
|
|
|
|
$startRange->endContainer->nextSibling ?: $startRange->endContainer, 0,
|
|
|
|
$nextStart, $nextStart->childNodes->length ?? 0
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
// This is the last section, so we want to go to the end of the rootnode
|
|
|
|
$betweenRange = new ImmutableRange(
|
|
|
|
$startRange->endContainer->nextSibling ?: $startRange->endContainer, 0,
|
|
|
|
$item->getRootNode(), $item->getRootNode()->childNodes->length
|
|
|
|
);
|
|
|
|
}
|
|
|
|
$fragment = $betweenRange->cloneContents();
|
|
|
|
CommentModifier::unwrapFragment( $fragment );
|
|
|
|
$output[$index]['othercontent'] = trim( DOMUtils::getFragmentInnerHTML( $fragment ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$result['threaditemshtml'] = $output;
|
2022-03-21 21:14:47 +00:00
|
|
|
}
|
2022-02-04 23:21:29 +00:00
|
|
|
|
|
|
|
$this->getResult()->addValue( null, $this->getModuleName(), $result );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
public function getAllowedParams() {
|
|
|
|
return [
|
|
|
|
'page' => [
|
|
|
|
ParamValidator::PARAM_REQUIRED => true,
|
|
|
|
ApiBase::PARAM_HELP_MSG => 'apihelp-visualeditoredit-param-page',
|
|
|
|
],
|
|
|
|
'oldid' => null,
|
2022-03-21 21:14:47 +00:00
|
|
|
'prop' => [
|
2022-04-03 23:26:15 +00:00
|
|
|
ParamValidator::PARAM_DEFAULT => 'transcludedfrom',
|
|
|
|
ParamValidator::PARAM_ISMULTI => true,
|
|
|
|
ParamValidator::PARAM_TYPE => [
|
2022-03-21 21:14:47 +00:00
|
|
|
'transcludedfrom',
|
|
|
|
'threaditemshtml'
|
|
|
|
],
|
|
|
|
ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
|
|
|
|
],
|
2022-02-04 23:21:29 +00:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
public function needsToken() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
public function isInternal() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
public function isWriteMode() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|