30use Wikimedia\Timestamp\TimestampFormat as TS;
45 private $isEnotifEnabled;
51 private $readOnlyMode;
54 private $revisionLookup;
57 private $talkPageNotificationManager;
60 private $watchedItemStore;
69 private $wikiPageFactory;
72 private array $watchlistLabels = [];
90 private $notificationTimestampCache = [];
117 $this->hookRunner =
new HookRunner( $hookContainer );
118 $this->readOnlyMode = $readOnlyMode;
119 $this->revisionLookup = $revisionLookup;
120 $this->talkPageNotificationManager = $talkPageNotificationManager;
121 $this->watchedItemStore = $watchedItemStore;
122 $this->userFactory = $userFactory;
123 $this->nsInfo = $nsInfo;
124 $this->wikiPageFactory = $wikiPageFactory;
137 if ( $this->readOnlyMode->isReadOnly() ) {
146 if ( !$this->isEnotifEnabled ) {
147 $this->talkPageNotificationManager->removeUserHasNewMessages( $user );
151 if ( !$performer->
isAllowed(
'editmywatchlist' ) ) {
156 if ( !$user->isRegistered() ) {
160 $this->watchedItemStore->resetAllNotificationTimestampsForUser( $user );
183 $oldRevDeprecated =
null
185 if ( func_num_args() > 3 ) {
186 wfDeprecatedMsg(
'Passing $oldid to ' . __METHOD__ .
' is deprecated since 1.46.' );
187 $oldid = $oldRev ?? 0;
188 $oldRev = $oldRevDeprecated;
190 $oldid = $oldRev?->getId() ?? 0;
193 if ( $this->readOnlyMode->isReadOnly() ) {
198 $userIdentity = $performer->
getUser();
201 $title->
getDBkey() === strtr( $userIdentity->getName(),
' ',
'_' )
204 if ( $userTalkPage ) {
207 } elseif ( !$oldRev ) {
208 $oldRev = $this->revisionLookup->getRevisionById( $oldid );
213 $this->talkPageNotificationManager->clearForPageView( $userIdentity, $oldRev );
216 if ( !$this->isEnotifEnabled ) {
220 if ( !$userIdentity->isRegistered() ) {
228 if ( !$performer->
isAllowed(
'editmywatchlist' ) ) {
237 $force = $userTalkPage ?
'force' :
'';
238 $this->watchedItemStore->resetNotificationTimestamp( $userIdentity, $title, $force, $oldid );
253 $cacheKey =
'u' . $user->
getId() .
'-' .
257 if ( array_key_exists( $cacheKey, $this->notificationTimestampCache ) ) {
258 return $this->notificationTimestampCache[ $cacheKey ];
261 $watchedItem = $this->watchedItemStore->getWatchedItem( $user, $title );
262 if ( $watchedItem ) {
263 $timestamp = $watchedItem->getNotificationTimestamp();
268 $this->notificationTimestampCache[ $cacheKey ] = $timestamp;
278 if ( !$this->nsInfo->
isWatchable( $target->getNamespace() ) ) {
282 if ( $target instanceof
PageIdentity && !$target->canExist() ) {
298 if ( $this->isWatchable( $target ) ) {
299 return $this->watchedItemStore->isWatched( $userIdentity, $target );
313 if ( $performer->isAllowed(
'viewmywatchlist' ) ) {
314 return $this->isWatchedIgnoringRights( $performer->
getUser(), $target );
327 if ( $this->isWatchable( $target ) ) {
328 return $this->watchedItemStore->isTempWatched( $userIdentity, $target );
342 if ( $performer->isAllowed(
'viewmywatchlist' ) ) {
343 return $this->isTempWatchedIgnoringRights( $performer->
getUser(), $target );
360 ?
string $expiry =
null
362 if ( !$this->isWatchable( $target ) ) {
363 return StatusValue::newFatal(
'watchlistnotwatchable' );
366 $wikiPage = $this->wikiPageFactory->newFromTitle( $target );
367 $title = $wikiPage->getTitle();
369 $status = Status::newFatal(
'hookaborted' );
371 if ( $this->hookRunner->onWatchArticle( $userIdentity, $wikiPage, $status, $expiry ) ) {
372 $status = StatusValue::newGood();
373 $this->watchedItemStore->addWatch( $userIdentity, $this->getSubjectPage( $title ), $expiry );
374 if ( $this->nsInfo->canHaveTalkPage( $title ) ) {
375 $this->watchedItemStore->addWatch( $userIdentity, $this->getTalkPage( $title ), $expiry );
377 $this->hookRunner->onWatchArticleComplete( $userIdentity, $wikiPage );
381 $user = $this->userFactory->newFromUserIdentity( $userIdentity );
382 $user->invalidateCache();
400 ?
string $expiry =
null
403 if ( !$performer->
isAllowed(
'editmywatchlist', $status ) ) {
407 return $this->addWatchIgnoringRights( $performer->
getUser(), $target, $expiry );
421 if ( !$this->isWatchable( $target ) ) {
422 return StatusValue::newFatal(
'watchlistnotwatchable' );
425 $wikiPage = $this->wikiPageFactory->newFromTitle( $target );
426 $title = $wikiPage->getTitle();
428 $status = Status::newFatal(
'hookaborted' );
430 if ( $this->hookRunner->onUnwatchArticle( $userIdentity, $wikiPage, $status ) ) {
431 $status = StatusValue::newGood();
432 $this->watchedItemStore->removeWatch( $userIdentity, $this->getSubjectPage( $title ) );
433 if ( $this->nsInfo->canHaveTalkPage( $title ) ) {
434 $this->watchedItemStore->removeWatch( $userIdentity, $this->getTalkPage( $title ) );
436 $this->hookRunner->onUnwatchArticleComplete( $userIdentity, $wikiPage );
440 $user = $this->userFactory->newFromUserIdentity( $userIdentity );
441 $user->invalidateCache();
452 private function getSubjectPage( PageReference $title ): PageReference {
453 if ( $this->nsInfo->isSubject( $title->getNamespace() ) ) {
456 return PageReferenceValue::localReference(
457 $this->nsInfo->getSubject( $title->getNamespace() ),
468 private function getTalkPage( PageReference $title ): PageReference {
469 if ( $this->nsInfo->isTalk( $title->getNamespace() ) ) {
472 return PageReferenceValue::localReference(
473 $this->nsInfo->getTalk( $title->getNamespace() ),
491 if ( !$performer->
isAllowed(
'editmywatchlist', $status ) ) {
495 return $this->removeWatchIgnoringRights( $performer->
getUser(), $target );
516 ?
string $expiry =
null,
517 ?array $labels =
null
520 if ( !$performer->getUser()->isRegistered() || $performer->isTemp() ) {
521 return StatusValue::newGood();
527 $oldWatchedItem = $this->watchedItemStore->getWatchedItem( $performer->
getUser(), $target );
528 $changingWatchStatus = (bool)$oldWatchedItem !== $watch;
529 if ( $oldWatchedItem && $expiry !==
null ) {
531 $oldWatchPeriod = $oldWatchedItem->getExpiry() ??
'infinity';
532 $changingWatchStatus = $changingWatchStatus ||
533 $oldWatchPeriod !== ExpiryDef::normalizeExpiry( $expiry, TS::MW );
536 $out = StatusValue::newGood();
537 if ( $changingWatchStatus ) {
541 $out = $this->addWatchIgnoringRights( $performer->
getUser(), $target, $expiry );
543 $out = $this->removeWatch( $performer, $target );
547 if ( $watch && $labels !==
null ) {
549 $assocPage = $this->nsInfo->getAssociatedPage( TitleValue::newFromPage( $target ) );
552 PageReferenceValue::localReference( $assocPage->getNamespace(), $assocPage->getDBkey() ),
554 $this->addOrRemoveLabels( $performer->
getUser(), $targets, $labels );
569 private function addOrRemoveLabels( UserIdentity $user, array $targets, array $newLabelIds ): void {
570 foreach ( $targets as $target ) {
571 $currentLabelIds = [];
572 $watchedItem = $this->watchedItemStore->getWatchedItem( $user, $target );
573 if ( $watchedItem ) {
574 foreach ( $watchedItem->getLabels() as $label ) {
575 $labelId = $label->getId();
576 if ( $labelId !==
null ) {
577 $currentLabelIds[] = $labelId;
581 $userLabelIds = array_keys( $this->loadWatchlistLabelsForUser( $user ) );
582 $submittedLabelIds = array_values( array_intersect( $newLabelIds, $userLabelIds ) );
583 $toAdd = array_values( array_diff( $submittedLabelIds, $currentLabelIds ) );
584 $toRemove = array_values( array_diff( $currentLabelIds, $submittedLabelIds ) );
586 $this->watchedItemStore->addLabels( $user, $targets, $toAdd );
589 $this->watchedItemStore->removeLabels( $user, $targets, $toRemove );
600 private function loadWatchlistLabelsForUser( UserIdentity $user ): array {
601 if ( !isset( $this->watchlistLabels[$user->getId()] ) ) {
602 $this->watchlistLabels[$user->getId()] = $this->watchlistLabelStore->loadAllForUser( $user );
605 return $this->watchlistLabels[$user->getId()];
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
if(!defined('MW_SETUP_CALLBACK'))
Service for creating WikiPage objects.
Manages user talk page notifications.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Interface for objects (potentially) representing an editable wiki page.
canExist()
Checks whether this PageIdentity represents a "proper" page, meaning that it could exist as an editab...