2020-05-15 17:19:03 +00:00
|
|
|
<?php
|
|
|
|
|
2022-04-08 00:38:27 +00:00
|
|
|
namespace MediaWiki\Extension\Notifications\Push;
|
2020-05-15 17:19:03 +00:00
|
|
|
|
2022-11-02 20:47:04 +00:00
|
|
|
use MediaWiki\Extension\Notifications\Mapper\AbstractMapper;
|
2020-05-15 17:19:03 +00:00
|
|
|
use MediaWiki\Storage\NameTableStore;
|
|
|
|
use Wikimedia\Rdbms\DBError;
|
2021-09-18 18:06:54 +00:00
|
|
|
use Wikimedia\Rdbms\IDatabase;
|
2020-05-15 17:19:03 +00:00
|
|
|
|
2022-11-02 20:47:04 +00:00
|
|
|
class SubscriptionManager extends AbstractMapper {
|
2020-05-15 17:19:03 +00:00
|
|
|
|
|
|
|
/** @var IDatabase */
|
|
|
|
private $dbw;
|
|
|
|
|
|
|
|
/** @var IDatabase */
|
|
|
|
private $dbr;
|
|
|
|
|
|
|
|
/** @var NameTableStore */
|
|
|
|
private $pushProviderStore;
|
|
|
|
|
2020-09-08 13:34:54 +00:00
|
|
|
/** @var NameTableStore */
|
|
|
|
private $pushTopicStore;
|
|
|
|
|
2020-08-12 21:33:06 +00:00
|
|
|
/** @var int */
|
|
|
|
private $maxSubscriptionsPerUser;
|
|
|
|
|
2020-05-15 17:19:03 +00:00
|
|
|
/**
|
|
|
|
* @param IDatabase $dbw primary DB connection (for writes)
|
|
|
|
* @param IDatabase $dbr replica DB connection (for reads)
|
|
|
|
* @param NameTableStore $pushProviderStore
|
2020-09-08 13:34:54 +00:00
|
|
|
* @param NameTableStore $pushTopicStore
|
2020-08-12 21:33:06 +00:00
|
|
|
* @param int $maxSubscriptionsPerUser
|
2020-05-15 17:19:03 +00:00
|
|
|
*/
|
|
|
|
public function __construct(
|
|
|
|
IDatabase $dbw,
|
|
|
|
IDatabase $dbr,
|
2020-08-12 21:33:06 +00:00
|
|
|
NameTableStore $pushProviderStore,
|
2020-09-08 13:34:54 +00:00
|
|
|
NameTableStore $pushTopicStore,
|
2020-08-12 21:33:06 +00:00
|
|
|
int $maxSubscriptionsPerUser
|
2020-05-15 17:19:03 +00:00
|
|
|
) {
|
|
|
|
parent::__construct();
|
|
|
|
$this->dbw = $dbw;
|
|
|
|
$this->dbr = $dbr;
|
|
|
|
$this->pushProviderStore = $pushProviderStore;
|
2020-09-08 13:34:54 +00:00
|
|
|
$this->pushTopicStore = $pushTopicStore;
|
2020-08-12 21:33:06 +00:00
|
|
|
$this->maxSubscriptionsPerUser = $maxSubscriptionsPerUser;
|
|
|
|
}
|
|
|
|
|
2020-05-15 17:19:03 +00:00
|
|
|
/**
|
2020-08-11 20:34:21 +00:00
|
|
|
* Store push subscription information for a central user ID.
|
2020-05-15 17:19:03 +00:00
|
|
|
* @param string $provider Provider name string (validated by presence in the PARAM_TYPE array)
|
|
|
|
* @param string $token Subscriber token provided by the push provider
|
2020-08-11 20:34:21 +00:00
|
|
|
* @param int $centralId
|
2020-08-03 18:27:47 +00:00
|
|
|
* @param string|null $topic APNS topic string
|
2020-06-09 23:08:14 +00:00
|
|
|
* @return bool true if the subscription was created; false if the token already exists
|
2020-05-15 17:19:03 +00:00
|
|
|
*/
|
2020-08-11 20:34:21 +00:00
|
|
|
public function create( string $provider, string $token, int $centralId, ?string $topic = null ): bool {
|
2021-07-23 13:23:31 +00:00
|
|
|
$subscriptions = $this->getSubscriptionsForUser( $centralId );
|
|
|
|
if ( count( $subscriptions ) >= $this->maxSubscriptionsPerUser ) {
|
|
|
|
// If we exceed the number of subscriptions for this user, then delete the oldest subscription
|
|
|
|
// before inserting the new one, making it behave like a circular buffer.
|
|
|
|
// (Find the oldest subscription by iterating, since their order in the DB is not guaranteed.)
|
|
|
|
$oldest = $subscriptions[0];
|
|
|
|
foreach ( $subscriptions as $subscription ) {
|
|
|
|
if ( $subscription->getUpdated() < $oldest->getUpdated() ) {
|
|
|
|
$oldest = $subscription;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$this->delete( [ $oldest->getToken() ], $centralId );
|
2020-08-12 21:33:06 +00:00
|
|
|
}
|
2020-09-08 13:34:54 +00:00
|
|
|
$topicId = $topic ? $this->pushTopicStore->acquireId( $topic ) : null;
|
2024-04-13 18:33:56 +00:00
|
|
|
$this->dbw->newInsertQueryBuilder()
|
|
|
|
->insertInto( 'echo_push_subscription' )
|
|
|
|
->ignore()
|
|
|
|
->row( [
|
2020-08-12 21:33:06 +00:00
|
|
|
'eps_user' => $centralId,
|
2020-05-15 17:19:03 +00:00
|
|
|
'eps_provider' => $this->pushProviderStore->acquireId( $provider ),
|
|
|
|
'eps_token' => $token,
|
|
|
|
'eps_token_sha256' => hash( 'sha256', $token ),
|
2020-08-03 18:27:47 +00:00
|
|
|
'eps_data' => null,
|
2020-09-08 13:34:54 +00:00
|
|
|
'eps_topic' => $topicId,
|
2020-05-15 17:19:03 +00:00
|
|
|
'eps_updated' => $this->dbw->timestamp()
|
2024-04-13 18:33:56 +00:00
|
|
|
] )
|
|
|
|
->caller( __METHOD__ )
|
|
|
|
->execute();
|
2020-06-09 23:08:14 +00:00
|
|
|
return (bool)$this->dbw->affectedRows();
|
2020-05-15 17:19:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-08-12 21:33:06 +00:00
|
|
|
* Get full data for all registered subscriptions for a user (by central ID).
|
2020-05-22 22:02:34 +00:00
|
|
|
* @param int $centralId
|
2024-03-15 08:27:36 +00:00
|
|
|
* @return Subscription[]
|
2020-05-15 17:19:03 +00:00
|
|
|
*/
|
2024-03-15 08:27:36 +00:00
|
|
|
public function getSubscriptionsForUser( int $centralId ): array {
|
2024-04-27 21:37:18 +00:00
|
|
|
$res = $this->dbr->newSelectQueryBuilder()
|
|
|
|
->select( '*' )
|
|
|
|
->from( 'echo_push_subscription' )
|
|
|
|
->join( 'echo_push_provider', null, 'eps_provider = epp_id' )
|
|
|
|
->leftJoin( 'echo_push_topic', null, 'eps_topic = ept_id' )
|
|
|
|
->where( [ 'eps_user' => $centralId ] )
|
|
|
|
->caller( __METHOD__ )
|
|
|
|
->fetchResultSet();
|
2020-05-15 17:19:03 +00:00
|
|
|
$result = [];
|
|
|
|
foreach ( $res as $row ) {
|
|
|
|
$result[] = Subscription::newFromRow( $row );
|
|
|
|
}
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-08-11 20:34:21 +00:00
|
|
|
* Delete one or more push subscriptions by token. Unless the current user is a push
|
|
|
|
* subscription manager, the query will also include the current central user ID as a condition.
|
|
|
|
* @param array $tokens Delete the subscription with these tokens
|
|
|
|
* @param int|null $centralId
|
2020-05-15 17:19:03 +00:00
|
|
|
* @return int number of rows deleted
|
|
|
|
* @throws DBError
|
|
|
|
*/
|
2024-10-26 13:05:00 +00:00
|
|
|
public function delete( array $tokens, ?int $centralId = null ): int {
|
2020-08-11 20:34:21 +00:00
|
|
|
$cond = [ 'eps_token' => $tokens ];
|
|
|
|
if ( $centralId ) {
|
|
|
|
$cond['eps_user'] = $centralId;
|
|
|
|
}
|
2024-04-12 19:50:43 +00:00
|
|
|
$this->dbw->newDeleteQueryBuilder()
|
|
|
|
->deleteFrom( 'echo_push_subscription' )
|
|
|
|
->where( $cond )
|
|
|
|
->caller( __METHOD__ )
|
|
|
|
->execute();
|
2020-05-15 17:19:03 +00:00
|
|
|
return $this->dbw->affectedRows();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|