2021-02-09 21:51:09 +00:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* DiscussionTools event dispatcher
|
|
|
|
*
|
|
|
|
* @file
|
|
|
|
* @ingroup Extensions
|
|
|
|
* @license MIT
|
|
|
|
*/
|
|
|
|
|
|
|
|
namespace MediaWiki\Extension\DiscussionTools\Notifications;
|
|
|
|
|
|
|
|
use ApiMain;
|
|
|
|
use DOMElement;
|
|
|
|
use EchoEvent;
|
|
|
|
use Error;
|
|
|
|
use FauxRequest;
|
|
|
|
use Iterator;
|
|
|
|
use MediaWiki\Extension\DiscussionTools\CommentParser;
|
2021-04-08 12:12:07 +00:00
|
|
|
use MediaWiki\Extension\DiscussionTools\Hooks\HookUtils;
|
2021-02-09 21:51:09 +00:00
|
|
|
use MediaWiki\Extension\DiscussionTools\SubscriptionItem;
|
|
|
|
use MediaWiki\MediaWikiServices;
|
|
|
|
use MediaWiki\Revision\RevisionRecord;
|
|
|
|
use MediaWiki\User\UserIdentity;
|
2021-04-08 12:12:07 +00:00
|
|
|
use Title;
|
2021-02-09 21:51:09 +00:00
|
|
|
use Wikimedia\Parsoid\Utils\DOMUtils;
|
|
|
|
|
|
|
|
class EventDispatcher {
|
|
|
|
/**
|
|
|
|
* @param RevisionRecord $revision
|
|
|
|
* @return CommentParser
|
|
|
|
*/
|
|
|
|
private static function getParsedRevision( RevisionRecord $revision ) : CommentParser {
|
|
|
|
$api = new ApiMain(
|
|
|
|
new FauxRequest(
|
|
|
|
[
|
|
|
|
'action' => 'parse',
|
|
|
|
'oldid' => $revision->getId()
|
|
|
|
],
|
|
|
|
/* was posted? */ true
|
|
|
|
),
|
|
|
|
/* enable write? */ true
|
|
|
|
);
|
|
|
|
|
|
|
|
$api->execute();
|
|
|
|
$data = $api->getResult()->getResultData();
|
|
|
|
|
|
|
|
$doc = DOMUtils::parseHTML( $data['parse']['text'] );
|
|
|
|
$container = $doc->getElementsByTagName( 'body' )->item( 0 );
|
|
|
|
if ( !( $container instanceof DOMElement ) ) {
|
|
|
|
throw new Error( 'Could not load revision for notifications' );
|
|
|
|
}
|
|
|
|
return CommentParser::newFromGlobalState( $container );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param array &$events
|
|
|
|
* @param RevisionRecord $newRevRecord
|
|
|
|
*/
|
|
|
|
public static function generateEventsForRevision( array &$events, RevisionRecord $newRevRecord ) {
|
|
|
|
$services = MediaWikiServices::getInstance();
|
2021-04-08 12:16:54 +00:00
|
|
|
$dtConfig = $services->getConfigFactory()->makeConfig( 'discussiontools' );
|
|
|
|
|
2021-04-08 12:30:28 +00:00
|
|
|
if ( $dtConfig->get( 'DiscussionTools_' . HookUtils::TOPICSUBSCRIPTION ) === 'unavailable' ) {
|
2021-04-08 12:16:54 +00:00
|
|
|
// Feature disabled for all users
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-02-09 21:51:09 +00:00
|
|
|
$revisionStore = $services->getRevisionStore();
|
|
|
|
$userFactory = $services->getUserFactory();
|
|
|
|
$oldRevRecord = $revisionStore->getPreviousRevision( $newRevRecord );
|
|
|
|
|
|
|
|
if ( $oldRevRecord === null ) {
|
|
|
|
// TODO: Handle page creation (oldRevRecord = null?)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-04-08 12:12:07 +00:00
|
|
|
$title = Title::newFromLinkTarget(
|
|
|
|
$newRevRecord->getPageAsLinkTarget()
|
|
|
|
);
|
|
|
|
if ( !HookUtils::isAvailableForTitle( $title ) ) {
|
|
|
|
// Not a talk page
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-04-21 12:30:12 +00:00
|
|
|
$user = $newRevRecord->getUser();
|
|
|
|
if ( !$user ) {
|
|
|
|
// User can be null if the user is deleted, but this is unlikely
|
|
|
|
// to be the case if the user just made an edit
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-02-09 21:51:09 +00:00
|
|
|
$oldParser = self::getParsedRevision( $oldRevRecord );
|
|
|
|
$newParser = self::getParsedRevision( $newRevRecord );
|
|
|
|
|
|
|
|
$newComments = [];
|
|
|
|
foreach ( $newParser->getCommentItems() as $newComment ) {
|
2021-04-21 12:30:12 +00:00
|
|
|
if (
|
|
|
|
$newComment->getAuthor() === $user->getName() &&
|
2021-04-21 12:34:07 +00:00
|
|
|
// Compare comments by name, as ID could be changed by a parent comment
|
|
|
|
// being moved/deleted. The downside is that multiple replies within the
|
|
|
|
// same minute will only fire one notification.
|
|
|
|
count( $oldParser->findCommentsByName( $newComment->getName() ) ) === 0
|
2021-04-21 12:30:12 +00:00
|
|
|
) {
|
2021-02-09 21:51:09 +00:00
|
|
|
$newComments[] = $newComment;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$mentionedUsers = [];
|
|
|
|
foreach ( $events as $event ) {
|
|
|
|
if ( $event['type'] === 'mention' || $event['type'] === 'mention-summary' ) {
|
|
|
|
// Array is keyed by user id so we can do a simple array merge
|
|
|
|
$mentionedUsers += $event['extra']['mentioned-users'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ( $newComments as $newComment ) {
|
|
|
|
$heading = $newComment->getHeading();
|
|
|
|
$events[] = [
|
|
|
|
'type' => 'dt-subscribed-new-comment',
|
2021-04-08 12:12:07 +00:00
|
|
|
'title' => $title,
|
2021-02-09 21:51:09 +00:00
|
|
|
'extra' => [
|
|
|
|
'subscribed-comment-name' => $heading->getName(),
|
|
|
|
'comment-id' => $newComment->getId(),
|
2021-04-21 12:36:29 +00:00
|
|
|
'comment-name' => $newComment->getName(),
|
2021-02-09 21:51:09 +00:00
|
|
|
'content' => $newComment->getBodyText( true ),
|
|
|
|
'section-title' => $heading->getText(),
|
|
|
|
'revid' => $newRevRecord->getId(),
|
|
|
|
'mentioned-users' => $mentionedUsers,
|
|
|
|
],
|
2021-04-21 12:30:12 +00:00
|
|
|
'agent' => $user,
|
2021-02-09 21:51:09 +00:00
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return all users subscribed to a comment
|
|
|
|
*
|
|
|
|
* @param EchoEvent $event
|
|
|
|
* @param int $batchSize
|
|
|
|
* @return UserIdentity[]|Iterator<UserIdentity>
|
|
|
|
*/
|
|
|
|
public static function locateSubscribedUsers( EchoEvent $event, $batchSize = 500 ) {
|
|
|
|
$commentName = $event->getExtraParam( 'subscribed-comment-name' );
|
|
|
|
|
|
|
|
$subscriptionStore = MediaWikiServices::getInstance()->getService( 'DiscussionTools.SubscriptionStore' );
|
|
|
|
$subscriptionItems = $subscriptionStore->getSubscriptionItemsForTopic(
|
|
|
|
$commentName,
|
|
|
|
1
|
|
|
|
);
|
|
|
|
|
|
|
|
// Update notified timestamps
|
|
|
|
$subscriptionStore->updateSubscriptionNotifiedTimestamp(
|
|
|
|
null,
|
|
|
|
$commentName
|
|
|
|
);
|
|
|
|
|
|
|
|
// TODD: Have this return an Iterator instead?
|
|
|
|
$users = array_map( function ( SubscriptionItem $item ) {
|
|
|
|
return $item->getUserIdentity();
|
|
|
|
}, $subscriptionItems );
|
|
|
|
|
|
|
|
return $users;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|