MediaWiki REL1_35
WatchAction.php
Go to the documentation of this file.
1<?php
25
31class WatchAction extends FormAction {
32
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,
244 User $user,
245 string $expiry = null
246 ) {
247 // User must be logged in, and either changing the watch state or at least the expiry.
248 if ( !$user->isLoggedIn() ) {
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(
291 User $user,
292 $checkRights = User::CHECK_USER_RIGHTS,
293 ?string $expiry = null
294 ) {
295 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
296 if ( $checkRights && !$permissionManager->userHasRight( $user, 'editmywatchlist' ) ) {
297 return User::newFatalPermissionDeniedStatus( 'editmywatchlist' );
298 }
299
300 $page = WikiPage::factory( $title );
301
302 $status = Status::newFatal( 'hookaborted' );
303 if ( Hooks::runner()->onWatchArticle( $user, $page, $status, $expiry ) ) {
304 $status = Status::newGood();
305 $user->addWatch( $title, $checkRights, $expiry );
306 Hooks::runner()->onWatchArticleComplete( $user, $page );
307 }
308
309 return $status;
310 }
311
319 public static function doUnwatch( Title $title, User $user ) {
320 if ( !MediaWikiServices::getInstance()
322 ->userHasRight( $user, 'editmywatchlist' ) ) {
323 return User::newFatalPermissionDeniedStatus( 'editmywatchlist' );
324 }
325
326 $page = WikiPage::factory( $title );
327
328 $status = Status::newFatal( 'hookaborted' );
329 if ( Hooks::runner()->onUnwatchArticle( $user, $page, $status ) ) {
330 $status = Status::newGood();
331 $user->removeWatch( $title );
332 Hooks::runner()->onUnwatchArticleComplete( $user, $page );
333 }
334
335 return $status;
336 }
337
347 public static function getWatchToken( Title $title, User $user, $action = 'watch' ) {
348 if ( $action != 'unwatch' ) {
349 $action = 'watch';
350 }
351 // This must match ApiWatch and ResourceLoaderUserOptionsModule
352 return $user->getEditToken( $action );
353 }
354
355 public function doesWrites() {
356 return true;
357 }
358}
getPermissionManager()
wfIsInfinity( $str)
Determine input string is represents as infinity.
getContext()
WikiPage Article ImagePage CategoryPage Page $page
Page on which we're performing the action.
Definition Action.php:53
getTitle()
Shortcut to get the Title object from the page.
Definition Action.php:299
getContext()
Get the IContextSource in use here.
Definition Action.php:215
getUser()
Shortcut to get the User being used for this instance.
Definition Action.php:249
IContextSource $context
IContextSource if specified; otherwise we'll use the Context from the Page.
Definition Action.php:66
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition Action.php:311
getRequest()
Get the WebRequest being used for this instance.
Definition Action.php:229
An action which shows a form and does something based on the input from the form.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:135
setSubmitTextMsg( $msg)
Set the text for the submit button to a message.
setWrapperLegendMsg( $msg)
Prompt the whole form to be wrapped in a "<fieldset>", with this message as its "<legend>" element.
setTokenSalt( $salt)
Set the salt for the edit token.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Represents a title within MediaWiki.
Definition Title.php:42
Redirect a user to the login page.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:60
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:3860
const IGNORE_USER_RIGHTS
Definition User.php:94
const CHECK_USER_RIGHTS
Definition User.php:89
removeWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Stop watching an article.
Definition User.php:3276
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS, ?string $expiry=null)
Watch an article.
Definition User.php:3256
isLoggedIn()
Get whether the user is registered.
Definition User.php:3079
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition User.php:4537
isAnon()
Get whether the user is anonymous.
Definition User.php:3087
Page addition to a user's watchlist.
static getExpiryOptions(MessageLocalizer $msgLocalizer, $watchedItem)
Get options and default for a watchlist expiry select list.
getDescription()
Returns the description that goes below the <h1> tag.
string $expiryFormFieldName
bool $watchlistExpiry
The value of the $wgWatchlistExpiry configuration variable.
requiresUnblock()
Whether this action can still be executed by a blocked user.
static getExpiryOptionsFromMessage(MessageLocalizer $msgLocalizer, ?string $lang=null)
Parse expiry options message.
getFormFields()
Get an HTMLForm descriptor array Stable to override.
static doUnwatch(Title $title, User $user)
Unwatch a page.
usesOOUI()
Whether the form should use OOUI Stable to override.
static doWatchOrUnwatch( $watch, Title $title, User $user, string $expiry=null)
Watch or unwatch a page.
alterForm(HTMLForm $form)
Play with the HTMLForm if you need to more substantially Stable to override.
false WatchedItem $watchedItem
static doWatch(Title $title, User $user, $checkRights=User::CHECK_USER_RIGHTS, ?string $expiry=null)
Watch a page.
__construct(Page $page, IContextSource $context=null)
Only public since 1.21.
static getWatchToken(Title $title, User $user, $action='watch')
Get token to watch (or unwatch) a page for a user.
checkCanExecute(User $user)
Checks if the given user (identified by an object) can perform this action.
doesWrites()
Stable to override.
onSuccess()
Show one of 8 possible success messages.
onSubmit( $data)
Process the form on POST submission.
getName()
Return the name of the action this object responds to.
Representation of a pair of user and title for watchlist entries.
getExpiryInDaysText(MessageLocalizer $msgLocalizer, $isDropdownOption=false)
Get days remaining until a watched item expires as a text.
getExpiry(?int $style=TS_MW)
When the watched item will expire.
Type definition for expiry timestamps.
Definition ExpiryDef.php:17
static parseOptionsMessage(string $msg)
Parse labels and values out of a comma- and colon-separated list of options, such as is used for expi...
Interface for objects which can provide a MediaWiki context on request.
Interface for localizing messages in MediaWiki.
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
Definition Page.php:30
if(!isset( $args[0])) $lang