MediaWiki master
WatchAction.php
Go to the documentation of this file.
1<?php
10namespace MediaWiki\Actions;
11
32use Wikimedia\Timestamp\TimestampFormat as TS;
33
39class WatchAction extends FormAction {
40
43
44 private bool $enableWatchlistLabels;
45
47 protected $expiryFormFieldName = 'expiry';
48
50 protected $watchedItem = false;
51
55 public function __construct(
56 Article $article,
58 private readonly WatchlistManager $watchlistManager,
59 private readonly WatchedItemStoreInterface $watchedItemStore,
60 protected readonly WatchlistLabelStore $watchlistLabelStore,
61 private readonly UserOptionsLookup $userOptionsLookup,
62 ) {
63 parent::__construct( $article, $context );
64 $this->watchlistExpiry = $this->getContext()->getConfig()->get( MainConfigNames::WatchlistExpiry );
65 $this->enableWatchlistLabels = $this->getContext()->getConfig()->get( MainConfigNames::EnableWatchlistLabels );
66 if ( $this->watchlistExpiry || $this->enableWatchlistLabels ) {
67 // The watchedItem is only used in this action's form if $wgWatchlistExpiry is enabled.
68 $this->watchedItem = $watchedItemStore->getWatchedItem(
69 $this->getUser(),
70 $this->getTitle()
71 );
72 }
73 }
74
76 public function getName() {
77 return 'watch';
78 }
79
81 public function requiresUnblock() {
82 return false;
83 }
84
86 protected function getDescription() {
87 return '';
88 }
89
91 public function onSubmit( $data ) {
92 // Even though we're never unwatching here, use WatchlistManager::setWatch()
93 // because it also checks for changed expiry.
94 $result = $this->watchlistManager->setWatch(
95 true,
96 $this->getAuthority(),
97 $this->getTitle(),
98 $this->getRequest()->getVal( 'wp' . $this->expiryFormFieldName )
99 );
100
101 $labelIds = $this->getRequest()->getArray( 'wplabels' );
102 if ( $labelIds ) {
103 $this->watchedItemStore->addLabels( $this->getUser(), [ $this->getTitle() ], $labelIds );
104 }
105
106 return Status::wrap( $result );
107 }
108
115 protected function checkCanExecute( User $user ) {
116 if ( !$user->isRegistered()
117 || ( $user->isTemp() && !$user->isAllowed( 'editmywatchlist' ) )
118 ) {
119 throw new UserNotLoggedIn( 'watchlistanontext', 'watchnologin' );
120 }
121
122 parent::checkCanExecute( $user );
123 }
124
126 public function getRestriction() {
127 return 'editmywatchlist';
128 }
129
131 protected function usesOOUI() {
132 return true;
133 }
134
136 protected function getFormFields() {
137 // If neither expiries or labels are enabled, return a simple confirmation message.
138 if ( !$this->watchlistExpiry && !$this->enableWatchlistLabels ) {
139 return [
140 'intro' => [
141 'type' => 'info',
142 'raw' => true,
143 'default' => $this->msg( 'confirm-watch-top' )->parse(),
144 ],
145 ];
146 }
147
148 $fields = [];
149
150 // Use a select-list of expiries, where the default is the user's
151 // preferred expiry time (or the existing watch duration if already temporarily watched).
152 if ( $this->watchlistExpiry ) {
153 $default = $this->userOptionsLookup->getOption( $this->getUser(), 'watchstar-expiry' );
154 $expiryOptions = static::getExpiryOptions( $this->getContext(), $this->watchedItem, $default );
156 'type' => 'select',
157 'label-message' => 'confirm-watch-label',
158 'options' => $expiryOptions['options'],
159 'default' => $expiryOptions['default'],
160 ];
161 }
162
163 // Show all of a user's labels as checkboxes.
164 if ( $this->enableWatchlistLabels ) {
165 $options = [];
166 foreach ( $this->watchlistLabelStore->loadAllForUser( $this->getUser() ) as $label ) {
167 $options[ htmlspecialchars( $label->getName() ) ] = $label->getId();
168 }
169 $fields[ 'labels' ] = [
170 'label-message' => 'watchlistlabels-watchaction-label',
171 'type' => 'multiselect',
172 'options' => $options,
173 ];
174 }
175
176 return $fields;
177 }
178
191 public static function getExpiryOptions(
192 MessageLocalizer $msgLocalizer,
194 string $defaultExpiry = 'infinite'
195 ) {
196 $expiryOptions = self::getExpiryOptionsFromMessage( $msgLocalizer );
197
198 if ( !in_array( $defaultExpiry, $expiryOptions ) ) {
199 $expiryOptions = array_merge( [ $defaultExpiry => $defaultExpiry ], $expiryOptions );
200 }
201
202 if ( $watchedItem instanceof WatchedItem && $watchedItem->getExpiry() ) {
203 // If it's already being temporarily watched, add the existing expiry as an option in the dropdown.
204 $currentExpiry = $watchedItem->getExpiry( TS::ISO_8601 );
205 $daysLeft = $watchedItem->getExpiryInDaysText( $msgLocalizer, true );
206 $expiryOptions = array_merge( [ $daysLeft => $currentExpiry ], $expiryOptions );
207
208 // Always preselect the existing expiry.
209 $defaultExpiry = $currentExpiry;
210 }
211
212 return [
213 'options' => $expiryOptions,
214 'default' => $defaultExpiry,
215 ];
216 }
217
227 public static function getExpiryOptionsFromMessage(
228 MessageLocalizer $msgLocalizer, ?string $lang = null
229 ): array {
230 $expiryOptionsMsg = $msgLocalizer->msg( 'watchlist-expiry-options' );
231 $optionsText = !$lang ? $expiryOptionsMsg->text() : $expiryOptionsMsg->inLanguage( $lang )->text();
233 $optionsText
234 );
235
236 $expiryOptions = [];
237 foreach ( $options as $label => $value ) {
238 if ( strtotime( $value ) || wfIsInfinity( $value ) ) {
239 $expiryOptions[$label] = $value;
240 }
241 }
242
243 // If message options is invalid try to recover by returning
244 // english options (T267611)
245 if ( !$expiryOptions && $expiryOptionsMsg->getLanguage()->getCode() !== 'en' ) {
246 return self::getExpiryOptionsFromMessage( $msgLocalizer, 'en' );
247 }
248
249 return $expiryOptions;
250 }
251
252 protected function alterForm( HTMLForm $form ) {
253 $msg = $this->watchlistExpiry && $this->watchedItem ? 'updatewatchlist' : 'addwatch';
254 $form->setWrapperLegendMsg( $msg );
255 $submitMsg = $this->watchlistExpiry ? 'confirm-watch-button-expiry' : 'confirm-watch-button';
256 $form->setSubmitTextMsg( $submitMsg );
257 $form->setTokenSalt( 'watch' );
258 }
259
272 public function onSuccess() {
273 // Add success message for watching and (optionally) expiry.
274 $submittedExpiry = $this->getContext()->getRequest()->getText( 'wp' . $this->expiryFormFieldName );
275 $this->getOutput()->addWikiMsg( $this->makeSuccessMessage( $submittedExpiry ) );
276 // Also add a line for labels if any were saved.
277 $labelIds = $this->getRequest()->getArray( 'wplabels' );
278 if ( $labelIds ) {
279 $this->getOutput()->addWikiMsg(
280 'watchlistlabels-watchaction-success',
281 count( $labelIds ),
282 SpecialPage::getTitleFor( 'WatchlistLabels' )->getFullText()
283 );
284 }
285 }
286
287 protected function makeSuccessMessage( string $submittedExpiry ): MessageValue {
288 $msgKey = $this->getTitle()->isTalkPage() ? 'addedwatchtext-talk' : 'addedwatchtext';
289 $params = [];
290 if ( $submittedExpiry ) {
291 // We can't use $this->watchedItem to get the expiry because it's not been saved at this
292 // point in the request and so its values are those from before saving.
293 $expiry = ExpiryDef::normalizeExpiry( $submittedExpiry, TS::ISO_8601 );
294
295 // If the expiry label isn't one of the predefined ones in the dropdown, calculate 'x days'.
296 $expiryDays = WatchedItem::calculateExpiryInDays( $expiry );
297 $defaultLabels = static::getExpiryOptionsFromMessage( $this->getContext() );
298 $localizedExpiry = array_search( $submittedExpiry, $defaultLabels );
299
300 // Determine which message to use, depending on whether this is a talk page or not
301 // and whether an expiry was selected.
302 $isTalk = $this->getTitle()->isTalkPage();
303 if ( wfIsInfinity( $expiry ) ) {
304 $msgKey = $isTalk ? 'addedwatchindefinitelytext-talk' : 'addedwatchindefinitelytext';
305 } elseif ( $expiryDays >= 1 ) {
306 $msgKey = $isTalk ? 'addedwatchexpirytext-talk' : 'addedwatchexpirytext';
307 $params[] = $localizedExpiry === false
308 ? $this->getContext()->msg( 'days', $expiryDays )->text()
309 : $localizedExpiry;
310 } else {
311 // Less than one day.
312 $msgKey = $isTalk ? 'addedwatchexpiryhours-talk' : 'addedwatchexpiryhours';
313 }
314 }
315 return MessageValue::new( $msgKey )->params( $this->getTitle()->getPrefixedText(), ...$params );
316 }
317
319 public function doesWrites() {
320 return true;
321 }
322}
323
325class_alias( WatchAction::class, 'WatchAction' );
wfIsInfinity( $str)
Determine input string is represents as infinity.
getContext()
Get the IContextSource in use here.
Definition Action.php:119
IContextSource null $context
IContextSource if specified; otherwise we'll use the Context from the Page.
Definition Action.php:66
getUser()
Shortcut to get the User being used for this instance.
Definition Action.php:153
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition Action.php:227
getTitle()
Shortcut to get the Title object from the page.
Definition Action.php:213
getRequest()
Get the WebRequest being used for this instance.
Definition Action.php:133
array $fields
The fields used to create the HTMLForm.
Definition Action.php:73
getAuthority()
Shortcut to get the Authority executing this instance.
Definition Action.php:163
An action which shows a form and does something based on the input from the form.
Page addition to a user's watchlist.
getName()
Return the name of the action this object responds to.1.17string Lowercase name
getRestriction()
Get the permission required to perform this action.Often, but not always, the same as the action name...
makeSuccessMessage(string $submittedExpiry)
__construct(Article $article, IContextSource $context, private readonly WatchlistManager $watchlistManager, private readonly WatchedItemStoreInterface $watchedItemStore, protected readonly WatchlistLabelStore $watchlistLabelStore, private readonly UserOptionsLookup $userOptionsLookup,)
Only public since 1.21.
onSubmit( $data)
Process the form on POST submission.If you don't want to do anything with the form,...
static getExpiryOptionsFromMessage(MessageLocalizer $msgLocalizer, ?string $lang=null)
Parse expiry options message.
onSuccess()
Show one of 8 possible success messages.
getFormFields()
Get an HTMLForm descriptor array.to override array
static getExpiryOptions(MessageLocalizer $msgLocalizer, $watchedItem, string $defaultExpiry='infinite')
Get options and default for a watchlist expiry select list.
usesOOUI()
Whether the form should use OOUI.to override bool
bool $watchlistExpiry
The value of the $wgWatchlistExpiry configuration variable.
getDescription()
Returns the description that goes below the <h1> element.1.17 to override string HTML
requiresUnblock()
Whether this action can still be executed by a blocked user.Implementations of this methods must alwa...
alterForm(HTMLForm $form)
Play with the HTMLForm if you need to more substantially.
false WatchedItem $watchedItem
Show an error when a user tries to do something they do not have the necessary permissions for.
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
Show an error when the user tries to do something whilst blocked.
Redirect a user to the login page or account creation page.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:207
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.
setSubmitTextMsg( $msg)
Set the text for the submit button to a message.
A class containing constants representing the names of configuration variables.
const EnableWatchlistLabels
Name constant for the EnableWatchlistLabels setting, for use with Config::get()
const WatchlistExpiry
Name constant for the WatchlistExpiry setting, for use with Config::get()
Legacy class representing an editable page and handling UI for some page actions.
Definition Article.php:64
Parent class for all special pages.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
Provides access to user options.
User class for the MediaWiki software.
Definition User.php:130
isAllowed(string $permission, ?PermissionStatus $status=null)
Checks whether this authority has the given permission in general.
Definition User.php:2149
isTemp()
Is the user an autocreated temporary user?
Definition User.php:3420
isRegistered()
Get whether the user is registered.
Definition User.php:2090
Representation of a pair of user and title for watchlist entries.
static calculateExpiryInDays(?string $expiry)
Get the number of days remaining until the given expiry time.
getExpiry(int|TS|null $style=TS::MW)
When the watched item will expire.
getExpiryInDaysText(MessageLocalizer $msgLocalizer, $isDropdownOption=false)
Get days remaining until a watched item expires as a text.
Service class for storage of watchlist labels.
Class for generating HTML <select> or <datalist> elements.
Definition XmlSelect.php:16
static parseOptionsMessage(string $msg)
Parse labels and values out of a comma- and colon-separated list of options, such as is used for expi...
Value object representing a message for i18n.
static new(string $key, array $params=[])
Static constructor for easier chaining of ->params() methods.
Type definition for expiry timestamps.
Definition ExpiryDef.php:18
Interface for objects which can provide a MediaWiki context on request.
Interface for localizing messages in MediaWiki.
getWatchedItem(UserIdentity $user, PageReference $target)
Get an item (may be cached)