1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
|
<?php
namespace MediaWiki\Api;
use MediaWiki\MediaWikiServices;
use MediaWiki\Page\PageIdentity;
use MediaWiki\User\Options\UserOptionsLookup;
use MediaWiki\User\User;
use MediaWiki\User\UserIdentity;
use MediaWiki\Watchlist\WatchedItemStoreInterface;
use MediaWiki\Watchlist\WatchlistManager;
use Wikimedia\ParamValidator\ParamValidator;
use Wikimedia\ParamValidator\TypeDef\ExpiryDef;
/**
* An ApiWatchlistTrait adds class properties and convenience methods for APIs that allow you to
* watch a page. This should ONLY be used in API modules that extend ApiBase.
* Also, it should not be used in ApiWatch, which has its own special handling.
*
* Note the class-level properties watchlistExpiryEnabled and watchlistMaxDuration must still be
* set in the API module's constructor.
*
* @ingroup API
* @since 1.35
*/
trait ApiWatchlistTrait {
/** @var bool Whether watchlist expiries are enabled. */
private $watchlistExpiryEnabled;
/** @var string Relative maximum expiry. */
private $watchlistMaxDuration;
private WatchlistManager $watchlistManager;
private UserOptionsLookup $userOptionsLookup;
private function initServices() {
// @phan-suppress-next-line PhanRedundantCondition Phan trusts the type hints too much
if ( isset( $this->watchlistManager ) && isset( $this->userOptionsLookup ) ) {
return;
}
// This trait is used outside of core and therefor fallback to global state - T263904
$services = MediaWikiServices::getInstance();
$this->watchlistManager ??= $services->getWatchlistManager();
$this->userOptionsLookup ??= $services->getUserOptionsLookup();
}
/**
* Get additional allow params specific to watchlisting.
* This should be merged in with the result of self::getAllowedParams().
*
* This purposefully does not include the deprecated 'watch' and 'unwatch'
* parameters that some APIs still accept.
*
* @param string[] $watchOptions
* @return array
*/
protected function getWatchlistParams( array $watchOptions = [] ): array {
if ( !$watchOptions ) {
$watchOptions = [
'watch',
'unwatch',
'preferences',
'nochange',
];
}
$result = [
'watchlist' => [
ParamValidator::PARAM_DEFAULT => 'preferences',
ParamValidator::PARAM_TYPE => $watchOptions,
],
];
if ( $this->watchlistExpiryEnabled ) {
$result['watchlistexpiry'] = [
ParamValidator::PARAM_TYPE => 'expiry',
ExpiryDef::PARAM_MAX => $this->watchlistMaxDuration,
ExpiryDef::PARAM_USE_MAX => true,
];
}
return $result;
}
/**
* Set a watch (or unwatch) based the based on a watchlist parameter.
* @param string $watch Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
* @param PageIdentity $page The page to change
* @param User $user The user to set watch/unwatch for
* @param string|null $userOption The user option to consider when $watch=preferences
* @param string|null $expiry Optional expiry timestamp in any format acceptable to wfTimestamp(),
* null will not create expiries, or leave them unchanged should they already exist.
*/
protected function setWatch(
string $watch,
PageIdentity $page,
User $user,
?string $userOption = null,
?string $expiry = null
): void {
$value = $this->getWatchlistValue( $watch, $page, $user, $userOption );
$this->watchlistManager->setWatch( $value, $user, $page, $expiry );
}
/**
* Return true if we're to watch the page, false if not.
* @param string $watchlist Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
* @param PageIdentity $page The page under consideration
* @param User $user The user get the value for.
* @param string|null $userOption The user option to consider when $watchlist=preferences.
* If not set will use watchdefault always and watchcreations if $page doesn't exist.
* @return bool
*/
protected function getWatchlistValue(
string $watchlist,
PageIdentity $page,
User $user,
?string $userOption = null
): bool {
$this->initServices();
$userWatching = $this->watchlistManager->isWatchedIgnoringRights( $user, $page );
switch ( $watchlist ) {
case 'watch':
return true;
case 'unwatch':
return false;
case 'preferences':
// If the user is already watching, don't bother checking
if ( $userWatching ) {
return true;
}
// If the user is a bot, act as 'nochange' to avoid big watchlists on single users
if ( $user->isBot() ) {
return $userWatching;
}
// If no user option was passed, use watchdefault and watchcreations
if ( $userOption === null ) {
return $this->userOptionsLookup->getBoolOption( $user, 'watchdefault' ) ||
( $this->userOptionsLookup->getBoolOption( $user, 'watchcreations' ) && !$page->exists() );
}
// Watch the article based on the user preference
return $this->userOptionsLookup->getBoolOption( $user, $userOption );
// case 'nochange':
default:
return $userWatching;
}
}
/**
* Get formatted expiry from the given parameters, or null if no expiry was provided.
* @param array $params Request parameters passed to the API.
* @return string|null
*/
protected function getExpiryFromParams( array $params ): ?string {
$watchlistExpiry = null;
if ( $this->watchlistExpiryEnabled && isset( $params['watchlistexpiry'] ) ) {
$watchlistExpiry = ApiResult::formatExpiry( $params['watchlistexpiry'] );
}
return $watchlistExpiry;
}
/**
* Get existing expiry from the database.
*
* @param WatchedItemStoreInterface $store
* @param PageIdentity $page
* @param UserIdentity $user The user to get the expiry for.
* @return string|null
*/
protected function getWatchlistExpiry(
WatchedItemStoreInterface $store,
PageIdentity $page,
UserIdentity $user
): ?string {
$watchedItem = $store->getWatchedItem( $user, $page );
if ( $watchedItem ) {
$expiry = $watchedItem->getExpiry();
if ( $expiry !== null ) {
return ApiResult::formatExpiry( $expiry );
}
}
return null;
}
}
/** @deprecated class alias since 1.43 */
class_alias( ApiWatchlistTrait::class, 'ApiWatchlistTrait' );
|