mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/DiscussionTools
synced 2024-11-24 00:13:36 +00:00
Service to interact with topic subscriptions
Bug: T264885 Change-Id: Ie9592de655f50e1d0cf02a7f795b5203398a9696
This commit is contained in:
parent
5b8646f73f
commit
86be6f83da
|
@ -383,6 +383,9 @@
|
|||
"class": "MediaWiki\\Extension\\DiscussionTools\\Hooks\\TagHooks"
|
||||
}
|
||||
},
|
||||
"ServiceWiringFiles": [
|
||||
"includes/ServiceWiring.php"
|
||||
],
|
||||
"DefaultUserOptions": {
|
||||
"discussiontools-editmode": "",
|
||||
"discussiontools-newtopictool": 1,
|
||||
|
|
15
includes/ServiceWiring.php
Normal file
15
includes/ServiceWiring.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace MediaWiki\Extension\DiscussionTools;
|
||||
|
||||
use MediaWiki\MediaWikiServices;
|
||||
|
||||
return [
|
||||
'DiscussionTools.SubscriptionStore' => static function ( MediaWikiServices $services ) : SubscriptionStore {
|
||||
return new SubscriptionStore(
|
||||
$services->getDBLoadBalancerFactory(),
|
||||
$services->getReadOnlyMode(),
|
||||
$services->getUserFactory()
|
||||
);
|
||||
}
|
||||
];
|
91
includes/SubscriptionItem.php
Normal file
91
includes/SubscriptionItem.php
Normal file
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
namespace MediaWiki\Extension\DiscussionTools;
|
||||
|
||||
use MediaWiki\Linker\LinkTarget;
|
||||
use MediaWiki\User\UserIdentity;
|
||||
|
||||
/**
|
||||
* Representation of a subscription to a given topic.
|
||||
*/
|
||||
class SubscriptionItem {
|
||||
private $itemName;
|
||||
private $linkTarget;
|
||||
private $user;
|
||||
private $state;
|
||||
private $createdTimestamp;
|
||||
private $notifiedTimestamp;
|
||||
|
||||
/**
|
||||
* @param UserIdentity $user
|
||||
* @param string $itemName
|
||||
* @param LinkTarget $linkTarget
|
||||
* @param int $state 1/0 for watched/muted
|
||||
* @param string|null $createdTimestamp When the subscription was created
|
||||
* @param string|null $notifiedTimestamp When the item subscribed to last tried to trigger
|
||||
* a notification (even if muted).
|
||||
*/
|
||||
public function __construct(
|
||||
UserIdentity $user,
|
||||
string $itemName,
|
||||
linkTarget $linkTarget,
|
||||
int $state,
|
||||
?string $createdTimestamp,
|
||||
?string $notifiedTimestamp
|
||||
) {
|
||||
$this->user = $user;
|
||||
$this->itemName = $itemName;
|
||||
$this->linkTarget = $linkTarget;
|
||||
$this->state = $state;
|
||||
$this->createdTimestamp = $createdTimestamp;
|
||||
$this->notifiedTimestamp = $notifiedTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UserIdentity
|
||||
*/
|
||||
public function getUserIdentity() : UserIdentity {
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getItemName() : string {
|
||||
return $this->itemName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LinkTarget
|
||||
*/
|
||||
public function getLinkTarget() : LinkTarget {
|
||||
return $this->linkTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the creation timestamp of this entry.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCreatedTimestamp() {
|
||||
return $this->createdTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification timestamp of this entry.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getNotificationTimestamp() {
|
||||
return $this->notifiedTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the notification is muted
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isMuted() : bool {
|
||||
return $this->state === 0;
|
||||
}
|
||||
}
|
315
includes/SubscriptionStore.php
Normal file
315
includes/SubscriptionStore.php
Normal file
|
@ -0,0 +1,315 @@
|
|||
<?php
|
||||
|
||||
namespace MediaWiki\Extension\DiscussionTools;
|
||||
|
||||
use MediaWiki\Linker\LinkTarget;
|
||||
use MediaWiki\User\UserFactory;
|
||||
use MediaWiki\User\UserIdentity;
|
||||
use ReadOnlyMode;
|
||||
use stdClass;
|
||||
use TitleValue;
|
||||
use Wikimedia\Rdbms\IDatabase;
|
||||
use Wikimedia\Rdbms\ILBFactory;
|
||||
use Wikimedia\Rdbms\ILoadBalancer;
|
||||
use Wikimedia\Rdbms\IResultWrapper;
|
||||
|
||||
// use Wikimedia\ParamValidator\TypeDef\ExpiryDef;
|
||||
// use Wikimedia\Timestamp\ConvertibleTimestamp;
|
||||
|
||||
class SubscriptionStore {
|
||||
/** @var ILBFactory */
|
||||
private $lbFactory;
|
||||
|
||||
/** @var ILoadBalancer */
|
||||
private $loadBalancer;
|
||||
|
||||
/** @var ReadOnlyMode */
|
||||
private $readOnlyMode;
|
||||
|
||||
/** @var UserFactory */
|
||||
private $userFactory;
|
||||
|
||||
/**
|
||||
* @param ILBFactory $lbFactory
|
||||
* @param ReadOnlyMode $readOnlyMode
|
||||
* @param UserFactory $userFactory
|
||||
*/
|
||||
public function __construct(
|
||||
ILBFactory $lbFactory,
|
||||
ReadOnlyMode $readOnlyMode,
|
||||
UserFactory $userFactory
|
||||
) {
|
||||
$this->lbFactory = $lbFactory;
|
||||
$this->loadBalancer = $lbFactory->getMainLB();
|
||||
|
||||
$this->userFactory = $userFactory;
|
||||
$this->readOnlyMode = $readOnlyMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $dbIndex DB_MASTER or DB_REPLICA
|
||||
*
|
||||
* @return IDatabase
|
||||
*/
|
||||
private function getConnectionRef( $dbIndex ) : IDatabase {
|
||||
return $this->loadBalancer->getConnectionRef( $dbIndex, [ 'watchlist' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IDatabase $db
|
||||
* @param UserIdentity|null $user
|
||||
* @param string|null $itemName
|
||||
* @param int|null $state
|
||||
* @return IResultWrapper|false
|
||||
*/
|
||||
private function fetchSubscriptions(
|
||||
IDatabase $db,
|
||||
?UserIdentity $user = null,
|
||||
?string $itemName = null,
|
||||
?int $state = null
|
||||
) {
|
||||
$conditions = [];
|
||||
|
||||
if ( $user ) {
|
||||
$conditions[ 'sub_user' ] = $user->getId();
|
||||
}
|
||||
|
||||
if ( $itemName ) {
|
||||
$conditions[ 'sub_item' ] = $itemName;
|
||||
}
|
||||
|
||||
if ( $state !== null ) {
|
||||
$conditions[ 'sub_state' ] = $state;
|
||||
}
|
||||
|
||||
return $db->select(
|
||||
'discussiontools_subscription',
|
||||
[
|
||||
'sub_user', 'sub_item', 'sub_namespace', 'sub_title', 'sub_section', 'sub_state',
|
||||
'sub_created', 'sub_notified'
|
||||
],
|
||||
$conditions,
|
||||
__METHOD__
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserIdentity $user
|
||||
* @param string|null $itemName
|
||||
* @param int|null $state
|
||||
* @param array $options
|
||||
* @return SubscriptionItem[]
|
||||
*/
|
||||
public function getSubscriptionItemsForUser(
|
||||
UserIdentity $user,
|
||||
?string $itemName = null,
|
||||
?int $state = null,
|
||||
array $options = []
|
||||
) : array {
|
||||
// Only a registered user can be subscribed
|
||||
if ( !$user->isRegistered() ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$options += [ 'forWrite' => false ];
|
||||
$db = $this->getConnectionRef( $options['forWrite'] ? DB_MASTER : DB_REPLICA );
|
||||
|
||||
$rows = $this->fetchSubscriptions(
|
||||
$db,
|
||||
$user,
|
||||
$itemName,
|
||||
$state
|
||||
);
|
||||
|
||||
if ( !$rows ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$items = [];
|
||||
foreach ( $rows as $row ) {
|
||||
$target = new TitleValue( (int)$row->sub_namespace, $row->sub_title, $row->sub_section );
|
||||
$items[] = $this->getSubscriptionItemFromRow( $user, $target, $row );
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $itemName
|
||||
* @param int|null $state
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
public function getSubscriptionItemsForTopic(
|
||||
string $itemName,
|
||||
?int $state = null,
|
||||
array $options = []
|
||||
) : array {
|
||||
$options += [ 'forWrite' => false ];
|
||||
$db = $this->getConnectionRef( $options['forWrite'] ? DB_MASTER : DB_REPLICA );
|
||||
|
||||
$rows = $this->fetchSubscriptions(
|
||||
$db,
|
||||
null,
|
||||
$itemName,
|
||||
$state
|
||||
);
|
||||
|
||||
if ( !$rows ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$items = [];
|
||||
foreach ( $rows as $row ) {
|
||||
$target = new TitleValue( (int)$row->sub_namespace, $row->sub_title, $row->sub_section );
|
||||
$user = $this->userFactory->newFromId( $row->sub_user );
|
||||
$items[] = $this->getSubscriptionItemFromRow( $user, $target, $row );
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserIdentity $user
|
||||
* @param LinkTarget $target
|
||||
* @param stdClass $row
|
||||
* @return SubscriptionItem
|
||||
*/
|
||||
private function getSubscriptionItemFromRow(
|
||||
UserIdentity $user,
|
||||
LinkTarget $target,
|
||||
stdClass $row
|
||||
) : SubscriptionItem {
|
||||
return new SubscriptionItem(
|
||||
$user,
|
||||
$row->sub_item,
|
||||
$target,
|
||||
$row->sub_state,
|
||||
$row->sub_created,
|
||||
$row->sub_notified
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserIdentity $user
|
||||
* @param LinkTarget $target
|
||||
* @param string $itemName
|
||||
* @return bool
|
||||
*/
|
||||
public function addSubscriptionForUser(
|
||||
UserIdentity $user,
|
||||
LinkTarget $target,
|
||||
string $itemName
|
||||
) : bool {
|
||||
if ( $this->readOnlyMode->isReadOnly() ) {
|
||||
return false;
|
||||
}
|
||||
// Only a registered user can subscribe
|
||||
if ( !$user->isRegistered() ) {
|
||||
return false;
|
||||
}
|
||||
$dbw = $this->getConnectionRef( DB_MASTER );
|
||||
$dbw->upsert(
|
||||
'discussiontools_subscription',
|
||||
[
|
||||
'sub_user' => $user->getId(),
|
||||
'sub_namespace' => $target->getNamespace(),
|
||||
'sub_title' => $target->getDBkey(),
|
||||
'sub_section' => $target->getFragment(),
|
||||
'sub_item' => $itemName,
|
||||
'sub_state' => 1,
|
||||
'sub_created' => $dbw->timestamp(),
|
||||
],
|
||||
[ [ 'sub_user', 'sub_item' ] ],
|
||||
[
|
||||
'sub_state' => 1,
|
||||
],
|
||||
__METHOD__
|
||||
);
|
||||
return (bool)$dbw->affectedRows();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserIdentity $user
|
||||
* @param string $itemName
|
||||
* @return bool
|
||||
*/
|
||||
public function removeSubscriptionForUser(
|
||||
UserIdentity $user,
|
||||
string $itemName
|
||||
) : bool {
|
||||
if ( $this->readOnlyMode->isReadOnly() ) {
|
||||
return false;
|
||||
}
|
||||
// Only a registered user can subscribe
|
||||
if ( !$user->isRegistered() ) {
|
||||
return false;
|
||||
}
|
||||
$dbw = $this->getConnectionRef( DB_MASTER );
|
||||
$dbw->update(
|
||||
'discussiontools_subscription',
|
||||
[ 'sub_state' => 0 ],
|
||||
[
|
||||
'sub_user' => $user->getId(),
|
||||
'sub_item' => $itemName,
|
||||
],
|
||||
__METHOD__
|
||||
);
|
||||
return (bool)$dbw->affectedRows();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field Timestamp field name
|
||||
* @param UserIdentity|null $user
|
||||
* @param string $itemName
|
||||
* @return bool
|
||||
*/
|
||||
private function updateSubscriptionTimestamp(
|
||||
string $field,
|
||||
?UserIdentity $user,
|
||||
string $itemName
|
||||
) : bool {
|
||||
if ( $this->readOnlyMode->isReadOnly() ) {
|
||||
return false;
|
||||
}
|
||||
$dbw = $this->getConnectionRef( DB_MASTER );
|
||||
|
||||
$conditions = [
|
||||
'sub_item' => $itemName,
|
||||
];
|
||||
|
||||
if ( $user ) {
|
||||
$conditions[ 'sub_user' ] = $user->getId();
|
||||
}
|
||||
|
||||
$dbw->update(
|
||||
'discussiontools_subscription',
|
||||
[ $field => $dbw->timestamp() ],
|
||||
$conditions,
|
||||
__METHOD__
|
||||
);
|
||||
return (bool)$dbw->affectedRows();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the notified timestamp on a subscription
|
||||
*
|
||||
* This field could be used in future to cleanup notifications
|
||||
* that are no longer needed (e.g. because the conversation has
|
||||
* been archived), so should be set for muted notifications too.
|
||||
*
|
||||
* @param UserIdentity|null $user
|
||||
* @param string $itemName
|
||||
* @return bool
|
||||
*/
|
||||
public function updateSubscriptionNotifiedTimestamp(
|
||||
?UserIdentity $user,
|
||||
string $itemName
|
||||
) : bool {
|
||||
return $this->updateSubscriptionTimestamp(
|
||||
'sub_notified',
|
||||
$user,
|
||||
$itemName
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue