MediaWiki  master
WatchAction.php
Go to the documentation of this file.
1 <?php
25 
31 class WatchAction extends FormAction {
32 
34  protected $watchlistExpiry;
35 
37  protected $expiryFormFieldName = 'expiry';
38 
40  protected $watchedItem = false;
41 
48  public function __construct( Page $page, IContextSource $context = null ) {
49  parent::__construct( $page, $context );
50  $this->watchlistExpiry = $this->getContext()->getConfig()->get( 'WatchlistExpiry' );
51  if ( $this->watchlistExpiry ) {
52  // The watchedItem is only used in this action's form if $wgWatchlistExpiry is enabled.
53  $this->watchedItem = MediaWikiServices::getInstance()
54  ->getWatchedItemStore()
55  ->getWatchedItem( $this->getUser(), $this->getTitle() );
56  }
57  }
58 
59  public function getName() {
60  return 'watch';
61  }
62 
63  public function requiresUnblock() {
64  return false;
65  }
66 
67  protected function getDescription() {
68  return '';
69  }
70 
71  public function onSubmit( $data ) {
72  $expiry = $this->getRequest()->getVal( 'wp' . $this->expiryFormFieldName );
73 
74  // Even though we're never unwatching here, use doWatchOrUnwatch() because it also checks for changed expiry.
75  return self::doWatchOrUnwatch( true, $this->getTitle(), $this->getUser(), $expiry );
76  }
77 
78  protected function checkCanExecute( User $user ) {
79  // Must be logged in
80  if ( $user->isAnon() ) {
81  throw new UserNotLoggedIn( 'watchlistanontext', 'watchnologin' );
82  }
83 
84  parent::checkCanExecute( $user );
85  }
86 
87  protected function usesOOUI() {
88  return true;
89  }
90 
91  protected function getFormFields() {
92  // If watchlist expiry is not enabled, return a simple confirmation message.
93  if ( !$this->watchlistExpiry ) {
94  return [
95  'intro' => [
96  'type' => 'info',
97  'vertical-label' => true,
98  'raw' => true,
99  'default' => $this->msg( 'confirm-watch-top' )->parse(),
100  ],
101  ];
102  }
103 
104  // Otherwise, use a select-list of expiries.
105  $expiryOptions = static::getExpiryOptions( $this->getContext(), $this->watchedItem );
106  return [
107  $this->expiryFormFieldName => [
108  'type' => 'select',
109  'label-message' => 'confirm-watch-label',
110  'options' => $expiryOptions['options'],
111  'default' => $expiryOptions['default'],
112  ]
113  ];
114  }
115 
128  public static function getExpiryOptions( MessageLocalizer $msgLocalizer, $watchedItem ) {
129  $expiryOptions = self::getExpiryOptionsFromMessage( $msgLocalizer );
130  $default = in_array( 'infinite', $expiryOptions )
131  ? 'infinite'
132  : current( $expiryOptions );
133  if ( $watchedItem instanceof WatchedItem && $watchedItem->getExpiry() ) {
134  // If it's already being temporarily watched,
135  // add the existing expiry as the default option in the dropdown.
136  $default = $watchedItem->getExpiry( TS_ISO_8601 );
137  $daysLeft = $watchedItem->getExpiryInDaysText( $msgLocalizer, true );
138  $expiryOptions = array_merge( [ $daysLeft => $default ], $expiryOptions );
139  }
140  return [
141  'options' => $expiryOptions,
142  'default' => $default,
143  ];
144  }
145 
154  private static function getExpiryOptionsFromMessage(
155  MessageLocalizer $msgLocalizer, ?string $lang = null
156  ) : array {
157  $expiryOptionsMsg = $msgLocalizer->msg( 'watchlist-expiry-options' );
158  $optionsText = !$lang ? $expiryOptionsMsg->text() : $expiryOptionsMsg->inLanguage( $lang )->text();
160  $optionsText
161  );
162 
163  $expiryOptions = [];
164  foreach ( $options as $label => $value ) {
165  if ( strtotime( $value ) || wfIsInfinity( $value ) ) {
166  $expiryOptions[$label] = $value;
167  }
168  }
169 
170  // If message options is invalid try to recover by returning
171  // english options (T267611)
172  if ( !$expiryOptions && $expiryOptionsMsg->getLanguage()->getCode() !== 'en' ) {
173  return self::getExpiryOptionsFromMessage( $msgLocalizer, 'en' );
174  }
175 
176  return $expiryOptions;
177  }
178 
179  protected function alterForm( HTMLForm $form ) {
180  $msg = $this->watchlistExpiry && $this->watchedItem ? 'updatewatchlist' : 'addwatch';
181  $form->setWrapperLegendMsg( $msg );
182  $submitMsg = $this->watchlistExpiry ? 'confirm-watch-button-expiry' : 'confirm-watch-button';
183  $form->setSubmitTextMsg( $submitMsg );
184  $form->setTokenSalt( 'watch' );
185  }
186 
199  public function onSuccess() {
200  $msgKey = $this->getTitle()->isTalkPage() ? 'addedwatchtext-talk' : 'addedwatchtext';
201  $expiryLabel = null;
202  $submittedExpiry = $this->getContext()->getRequest()->getText( 'wp' . $this->expiryFormFieldName );
203  if ( $submittedExpiry ) {
204  // We can't use $this->watcheditem to get the expiry because it's not been saved at this
205  // point in the request and so its values are those from before saving.
206  $expiry = ExpiryDef::normalizeExpiry( $submittedExpiry, TS_ISO_8601 );
207 
208  // If the expiry label isn't one of the predefined ones in the dropdown, calculate 'x days'.
209  $expiryDays = WatchedItem::calculateExpiryInDays( $expiry );
210  $defaultLabels = static::getExpiryOptions( $this->getContext(), null )['options'];
211  $localizedExpiry = array_search( $submittedExpiry, $defaultLabels );
212  $expiryLabel = $expiryDays && $localizedExpiry === false
213  ? $this->getContext()->msg( 'days', $expiryDays )->text()
214  : $localizedExpiry;
215 
216  // Determine which message to use, depending on whether this is a talk page or not
217  // and whether an expiry was selected.
218  $isTalk = $this->getTitle()->isTalkPage();
219  if ( wfIsInfinity( $expiry ) ) {
220  $msgKey = $isTalk ? 'addedwatchindefinitelytext-talk' : 'addedwatchindefinitelytext';
221  } elseif ( $expiryDays > 0 ) {
222  $msgKey = $isTalk ? 'addedwatchexpirytext-talk' : 'addedwatchexpirytext';
223  } elseif ( $expiryDays < 1 ) {
224  $msgKey = $isTalk ? 'addedwatchexpiryhours-talk' : 'addedwatchexpiryhours';
225  }
226  }
227  $this->getOutput()->addWikiMsg( $msgKey, $this->getTitle()->getPrefixedText(), $expiryLabel );
228  }
229 
241  public static function doWatchOrUnwatch(
242  $watch,
243  Title $title,
244  User $user,
245  string $expiry = null
246  ) {
247  // User must be registered, and either changing the watch state or at least the expiry.
248  if ( !$user->isRegistered() ) {
249  return Status::newGood();
250  }
251 
252  // Only run doWatch() or doUnwatch() if there's been a change in the watched status.
253  $oldWatchedItem = MediaWikiServices::getInstance()->getWatchedItemStore()
254  ->getWatchedItem( $user, $title );
255  $changingWatchStatus = (bool)$oldWatchedItem !== $watch;
256  if ( $oldWatchedItem && $expiry !== null ) {
257  // If there's an old watched item, a non-null change to the expiry requires an UPDATE.
258  $oldWatchPeriod = $oldWatchedItem->getExpiry() === null
259  ? 'infinity'
260  : $oldWatchedItem->getExpiry();
261  $changingWatchStatus = $changingWatchStatus ||
262  $oldWatchPeriod !== ExpiryDef::normalizeExpiry( $expiry, TS_MW );
263  }
264 
265  if ( $changingWatchStatus ) {
266  // If the user doesn't have 'editmywatchlist', we still want to
267  // allow them to add but not remove items via edits and such.
268  if ( $watch ) {
269  return self::doWatch( $title, $user, User::IGNORE_USER_RIGHTS, $expiry );
270  } else {
271  return self::doUnwatch( $title, $user );
272  }
273  }
274 
275  return Status::newGood();
276  }
277 
289  public static function doWatch(
290  Title $title,
291  User $user,
292  $checkRights = User::CHECK_USER_RIGHTS,
293  ?string $expiry = null
294  ) {
295  $services = MediaWikiServices::getInstance();
296  $permissionManager = $services->getPermissionManager();
297  if ( $checkRights && !$permissionManager->userHasRight( $user, 'editmywatchlist' ) ) {
298  return User::newFatalPermissionDeniedStatus( 'editmywatchlist' );
299  }
300 
301  $page = $services->getWikiPageFactory()->newFromTitle( $title );
302 
303  $status = Status::newFatal( 'hookaborted' );
304  if ( Hooks::runner()->onWatchArticle( $user, $page, $status, $expiry ) ) {
305  $status = Status::newGood();
306  $user->addWatch( $title, $checkRights, $expiry );
307  Hooks::runner()->onWatchArticleComplete( $user, $page );
308  }
309 
310  return $status;
311  }
312 
320  public static function doUnwatch( Title $title, User $user ) {
321  $services = MediaWikiServices::getInstance();
322  if ( !$services->getPermissionManager()
323  ->userHasRight( $user, 'editmywatchlist' ) ) {
324  return User::newFatalPermissionDeniedStatus( 'editmywatchlist' );
325  }
326 
327  $page = $services->getWikiPageFactory()->newFromTitle( $title );
328 
329  $status = Status::newFatal( 'hookaborted' );
330  if ( Hooks::runner()->onUnwatchArticle( $user, $page, $status ) ) {
331  $status = Status::newGood();
332  $user->removeWatch( $title );
333  Hooks::runner()->onUnwatchArticleComplete( $user, $page );
334  }
335 
336  return $status;
337  }
338 
348  public static function getWatchToken( Title $title, User $user, $action = 'watch' ) {
349  if ( $action != 'unwatch' ) {
350  $action = 'watch';
351  }
352  // This must match ApiWatch and ResourceLoaderUserOptionsModule
353  return $user->getEditToken( $action );
354  }
355 
356  public function doesWrites() {
357  return true;
358  }
359 }
Page
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
Definition: Page.php:29
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
WatchAction\doWatch
static doWatch(Title $title, User $user, $checkRights=User::CHECK_USER_RIGHTS, ?string $expiry=null)
Watch a page.
Definition: WatchAction.php:289
User\isAnon
isAnon()
Get whether the user is anonymous.
Definition: User.php:2991
WatchAction\alterForm
alterForm(HTMLForm $form)
Play with the HTMLForm if you need to more substantially Stable to override.
Definition: WatchAction.php:179
WatchAction\onSuccess
onSuccess()
Show one of 8 possible success messages.
Definition: WatchAction.php:199
User\isRegistered
isRegistered()
Get whether the user is registered.
Definition: User.php:2973
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:166
Action\getRequest
getRequest()
Get the WebRequest being used for this instance.
Definition: Action.php:229
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
User\newFatalPermissionDeniedStatus
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:4392
WatchAction\requiresUnblock
requiresUnblock()
Whether this action can still be executed by a blocked user.
Definition: WatchAction.php:63
WatchAction\doUnwatch
static doUnwatch(Title $title, User $user)
Unwatch a page.
Definition: WatchAction.php:320
UserNotLoggedIn
Redirect a user to the login page.
Definition: UserNotLoggedIn.php:54
WatchAction\$expiryFormFieldName
string $expiryFormFieldName
Definition: WatchAction.php:37
WatchAction\getExpiryOptions
static getExpiryOptions(MessageLocalizer $msgLocalizer, $watchedItem)
Get options and default for a watchlist expiry select list.
Definition: WatchAction.php:128
HTMLForm\setTokenSalt
setTokenSalt( $salt)
Set the salt for the edit token.
Definition: HTMLForm.php:1053
FormAction
An action which shows a form and does something based on the input from the form.
Definition: FormAction.php:30
WatchAction\getDescription
getDescription()
Returns the description that goes below the <h1> tag.
Definition: WatchAction.php:67
WatchAction
Page addition to a user's watchlist.
Definition: WatchAction.php:31
XmlSelect\parseOptionsMessage
static parseOptionsMessage(string $msg)
Parse labels and values out of a comma- and colon-separated list of options, such as is used for expi...
Definition: XmlSelect.php:145
WatchAction\onSubmit
onSubmit( $data)
Process the form on POST submission.
Definition: WatchAction.php:71
MessageLocalizer
Interface for localizing messages in MediaWiki.
Definition: MessageLocalizer.php:29
WatchAction\getFormFields
getFormFields()
Get an HTMLForm descriptor array Stable to override.
Definition: WatchAction.php:91
Action\getContext
getContext()
Get the IContextSource in use here.
Definition: Action.php:215
WatchedItem\getExpiry
getExpiry(?int $style=TS_MW)
When the watched item will expire.
Definition: WatchedItem.php:145
Wikimedia\ParamValidator\TypeDef\ExpiryDef
Type definition for expiry timestamps.
Definition: ExpiryDef.php:17
WatchAction\doesWrites
doesWrites()
Stable to override.
Definition: WatchAction.php:356
MessageLocalizer\msg
msg( $key,... $params)
This is the method for getting translated interface messages.
WatchAction\checkCanExecute
checkCanExecute(User $user)
Checks if the given user (identified by an object) can perform this action.
Definition: WatchAction.php:78
$title
$title
Definition: testCompression.php:38
User\CHECK_USER_RIGHTS
const CHECK_USER_RIGHTS
Definition: User.php:85
WatchedItem\calculateExpiryInDays
static calculateExpiryInDays(?string $expiry)
Get the number of days remaining until the given expiry time.
Definition: WatchedItem.php:187
WatchAction\getExpiryOptionsFromMessage
static getExpiryOptionsFromMessage(MessageLocalizer $msgLocalizer, ?string $lang=null)
Parse expiry options message.
Definition: WatchAction.php:154
Action\getUser
getUser()
Shortcut to get the User being used for this instance.
Definition: Action.php:249
Action\$context
IContextSource $context
IContextSource if specified; otherwise we'll use the Context from the Page.
Definition: Action.php:66
WatchAction\$watchlistExpiry
bool $watchlistExpiry
The value of the $wgWatchlistExpiry configuration variable.
Definition: WatchAction.php:34
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
Hooks\runner
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:172
wfIsInfinity
wfIsInfinity( $str)
Determine input string is represents as infinity.
Definition: GlobalFunctions.php:2763
WatchedItem
Representation of a pair of user and title for watchlist entries.
Definition: WatchedItem.php:35
User\addWatch
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS, ?string $expiry=null)
Watch an article.
Definition: User.php:3158
Action\getTitle
getTitle()
Shortcut to get the Title object from the page.
Definition: Action.php:299
User\removeWatch
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition: User.php:3184
WatchAction\doWatchOrUnwatch
static doWatchOrUnwatch( $watch, Title $title, User $user, string $expiry=null)
Watch or unwatch a page.
Definition: WatchAction.php:241
WatchAction\__construct
__construct(Page $page, IContextSource $context=null)
Only public since 1.21.
Definition: WatchAction.php:48
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:55
WatchAction\getName
getName()
Return the name of the action this object responds to.
Definition: WatchAction.php:59
HTMLForm\setSubmitTextMsg
setSubmitTextMsg( $msg)
Set the text for the submit button to a message.
Definition: HTMLForm.php:1419
Title
Represents a title within MediaWiki.
Definition: Title.php:46
WatchedItem\getExpiryInDaysText
getExpiryInDaysText(MessageLocalizer $msgLocalizer, $isDropdownOption=false)
Get days remaining until a watched item expires as a text.
Definition: WatchedItem.php:213
Action\$page
WikiPage Article ImagePage CategoryPage Page $page
Page on which we're performing the action.
Definition: Action.php:53
Action\msg
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition: Action.php:311
HTMLForm\setWrapperLegendMsg
setWrapperLegendMsg( $msg)
Prompt the whole form to be wrapped in a "<fieldset>", with this message as its "<legend>" element.
Definition: HTMLForm.php:1606
Action\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: Action.php:239
WatchAction\getWatchToken
static getWatchToken(Title $title, User $user, $action='watch')
Get token to watch (or unwatch) a page for a user.
Definition: WatchAction.php:348
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:90
WatchAction\$watchedItem
false WatchedItem $watchedItem
Definition: WatchAction.php:40
User\getEditToken
getEditToken( $salt='', $request=null)
Initialize (if necessary) and return a session token value which can be used in edit forms to show th...
Definition: User.php:3774
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:56
WatchAction\usesOOUI
usesOOUI()
Whether the form should use OOUI Stable to override.
Definition: WatchAction.php:87
HTMLForm
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition: HTMLForm.php:140