Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
88.89% |
80 / 90 |
|
85.71% |
12 / 14 |
CRAP | |
0.00% |
0 / 1 |
WatchAction | |
88.89% |
80 / 90 |
|
85.71% |
12 / 14 |
43.31 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
getName | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
requiresUnblock | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDescription | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
onSubmit | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
checkCanExecute | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
4 | |||
getRestriction | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
usesOOUI | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getFormFields | |
47.06% |
8 / 17 |
|
0.00% |
0 / 1 |
2.59 | |||
getExpiryOptions | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
4 | |||
getExpiryOptionsFromMessage | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
7 | |||
alterForm | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
4 | |||
onSuccess | |
94.74% |
18 / 19 |
|
0.00% |
0 / 1 |
11.02 | |||
doesWrites | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | /** |
3 | * Performs the watch actions on a page |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License |
16 | * along with this program; if not, write to the Free Software |
17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA |
18 | * |
19 | * @file |
20 | * @ingroup Actions |
21 | */ |
22 | |
23 | use MediaWiki\Context\IContextSource; |
24 | use MediaWiki\MainConfigNames; |
25 | use MediaWiki\Status\Status; |
26 | use MediaWiki\User\User; |
27 | use MediaWiki\Watchlist\WatchlistManager; |
28 | use Wikimedia\ParamValidator\TypeDef\ExpiryDef; |
29 | |
30 | /** |
31 | * Page addition to a user's watchlist |
32 | * |
33 | * @ingroup Actions |
34 | */ |
35 | class WatchAction extends FormAction { |
36 | |
37 | /** @var bool The value of the $wgWatchlistExpiry configuration variable. */ |
38 | protected $watchlistExpiry; |
39 | |
40 | /** @var string */ |
41 | protected $expiryFormFieldName = 'expiry'; |
42 | |
43 | /** @var false|WatchedItem */ |
44 | protected $watchedItem = false; |
45 | |
46 | private WatchlistManager $watchlistManager; |
47 | |
48 | /** |
49 | * Only public since 1.21 |
50 | * |
51 | * @param Article $article |
52 | * @param IContextSource $context |
53 | * @param WatchlistManager $watchlistManager |
54 | * @param WatchedItemStore $watchedItemStore |
55 | */ |
56 | public function __construct( |
57 | Article $article, |
58 | IContextSource $context, |
59 | WatchlistManager $watchlistManager, |
60 | WatchedItemStore $watchedItemStore |
61 | ) { |
62 | parent::__construct( $article, $context ); |
63 | $this->watchlistExpiry = $this->getContext()->getConfig()->get( MainConfigNames::WatchlistExpiry ); |
64 | if ( $this->watchlistExpiry ) { |
65 | // The watchedItem is only used in this action's form if $wgWatchlistExpiry is enabled. |
66 | $this->watchedItem = $watchedItemStore->getWatchedItem( |
67 | $this->getUser(), |
68 | $this->getTitle() |
69 | ); |
70 | } |
71 | $this->watchlistManager = $watchlistManager; |
72 | } |
73 | |
74 | public function getName() { |
75 | return 'watch'; |
76 | } |
77 | |
78 | public function requiresUnblock() { |
79 | return false; |
80 | } |
81 | |
82 | protected function getDescription() { |
83 | return ''; |
84 | } |
85 | |
86 | public function onSubmit( $data ) { |
87 | // Even though we're never unwatching here, use WatchlistManager::setWatch() |
88 | // because it also checks for changed expiry. |
89 | $result = $this->watchlistManager->setWatch( |
90 | true, |
91 | $this->getAuthority(), |
92 | $this->getTitle(), |
93 | $this->getRequest()->getVal( 'wp' . $this->expiryFormFieldName ) |
94 | ); |
95 | |
96 | return Status::wrap( $result ); |
97 | } |
98 | |
99 | /** |
100 | * @throws UserNotLoggedIn |
101 | * @throws PermissionsError |
102 | * @throws ReadOnlyError |
103 | * @throws UserBlockedError |
104 | */ |
105 | protected function checkCanExecute( User $user ) { |
106 | if ( !$user->isRegistered() |
107 | || ( $user->isTemp() && !$user->isAllowed( 'editmywatchlist' ) ) |
108 | ) { |
109 | throw new UserNotLoggedIn( 'watchlistanontext', 'watchnologin' ); |
110 | } |
111 | |
112 | parent::checkCanExecute( $user ); |
113 | } |
114 | |
115 | public function getRestriction() { |
116 | return 'editmywatchlist'; |
117 | } |
118 | |
119 | protected function usesOOUI() { |
120 | return true; |
121 | } |
122 | |
123 | protected function getFormFields() { |
124 | // If watchlist expiry is not enabled, return a simple confirmation message. |
125 | if ( !$this->watchlistExpiry ) { |
126 | return [ |
127 | 'intro' => [ |
128 | 'type' => 'info', |
129 | 'raw' => true, |
130 | 'default' => $this->msg( 'confirm-watch-top' )->parse(), |
131 | ], |
132 | ]; |
133 | } |
134 | |
135 | // Otherwise, use a select-list of expiries. |
136 | $expiryOptions = static::getExpiryOptions( $this->getContext(), $this->watchedItem ); |
137 | return [ |
138 | $this->expiryFormFieldName => [ |
139 | 'type' => 'select', |
140 | 'label-message' => 'confirm-watch-label', |
141 | 'options' => $expiryOptions['options'], |
142 | 'default' => $expiryOptions['default'], |
143 | ] |
144 | ]; |
145 | } |
146 | |
147 | /** |
148 | * Get options and default for a watchlist expiry select list. If an expiry time is provided, it |
149 | * will be added to the top of the list as 'x days left'. |
150 | * |
151 | * @since 1.35 |
152 | * @todo Move this somewhere better when it's being used in more than just this action. |
153 | * |
154 | * @param MessageLocalizer $msgLocalizer |
155 | * @param WatchedItem|false $watchedItem |
156 | * |
157 | * @return mixed[] With keys `options` (string[]) and `default` (string). |
158 | */ |
159 | public static function getExpiryOptions( MessageLocalizer $msgLocalizer, $watchedItem ) { |
160 | $expiryOptions = self::getExpiryOptionsFromMessage( $msgLocalizer ); |
161 | $default = in_array( 'infinite', $expiryOptions ) |
162 | ? 'infinite' |
163 | : current( $expiryOptions ); |
164 | if ( $watchedItem instanceof WatchedItem && $watchedItem->getExpiry() ) { |
165 | // If it's already being temporarily watched, |
166 | // add the existing expiry as the default option in the dropdown. |
167 | $default = $watchedItem->getExpiry( TS_ISO_8601 ); |
168 | $daysLeft = $watchedItem->getExpiryInDaysText( $msgLocalizer, true ); |
169 | $expiryOptions = array_merge( [ $daysLeft => $default ], $expiryOptions ); |
170 | } |
171 | return [ |
172 | 'options' => $expiryOptions, |
173 | 'default' => $default, |
174 | ]; |
175 | } |
176 | |
177 | /** |
178 | * Parse expiry options message. Fallback to english options |
179 | * if translated options are invalid or broken |
180 | * |
181 | * @param MessageLocalizer $msgLocalizer |
182 | * @param string|null $lang |
183 | * @return string[] |
184 | */ |
185 | private static function getExpiryOptionsFromMessage( |
186 | MessageLocalizer $msgLocalizer, ?string $lang = null |
187 | ): array { |
188 | $expiryOptionsMsg = $msgLocalizer->msg( 'watchlist-expiry-options' ); |
189 | $optionsText = !$lang ? $expiryOptionsMsg->text() : $expiryOptionsMsg->inLanguage( $lang )->text(); |
190 | $options = XmlSelect::parseOptionsMessage( |
191 | $optionsText |
192 | ); |
193 | |
194 | $expiryOptions = []; |
195 | foreach ( $options as $label => $value ) { |
196 | if ( strtotime( $value ) || wfIsInfinity( $value ) ) { |
197 | $expiryOptions[$label] = $value; |
198 | } |
199 | } |
200 | |
201 | // If message options is invalid try to recover by returning |
202 | // english options (T267611) |
203 | if ( !$expiryOptions && $expiryOptionsMsg->getLanguage()->getCode() !== 'en' ) { |
204 | return self::getExpiryOptionsFromMessage( $msgLocalizer, 'en' ); |
205 | } |
206 | |
207 | return $expiryOptions; |
208 | } |
209 | |
210 | protected function alterForm( HTMLForm $form ) { |
211 | $msg = $this->watchlistExpiry && $this->watchedItem ? 'updatewatchlist' : 'addwatch'; |
212 | $form->setWrapperLegendMsg( $msg ); |
213 | $submitMsg = $this->watchlistExpiry ? 'confirm-watch-button-expiry' : 'confirm-watch-button'; |
214 | $form->setSubmitTextMsg( $submitMsg ); |
215 | $form->setTokenSalt( 'watch' ); |
216 | } |
217 | |
218 | /** |
219 | * Show one of 8 possible success messages. |
220 | * The messages are: |
221 | * 1. addedwatchtext |
222 | * 2. addedwatchtext-talk |
223 | * 3. addedwatchindefinitelytext |
224 | * 4. addedwatchindefinitelytext-talk |
225 | * 5. addedwatchexpirytext |
226 | * 6. addedwatchexpirytext-talk |
227 | * 7. addedwatchexpiryhours |
228 | * 8. addedwatchexpiryhours-talk |
229 | */ |
230 | public function onSuccess() { |
231 | $msgKey = $this->getTitle()->isTalkPage() ? 'addedwatchtext-talk' : 'addedwatchtext'; |
232 | $expiryLabel = null; |
233 | $submittedExpiry = $this->getContext()->getRequest()->getText( 'wp' . $this->expiryFormFieldName ); |
234 | if ( $submittedExpiry ) { |
235 | // We can't use $this->watcheditem to get the expiry because it's not been saved at this |
236 | // point in the request and so its values are those from before saving. |
237 | $expiry = ExpiryDef::normalizeExpiry( $submittedExpiry, TS_ISO_8601 ); |
238 | |
239 | // If the expiry label isn't one of the predefined ones in the dropdown, calculate 'x days'. |
240 | $expiryDays = WatchedItem::calculateExpiryInDays( $expiry ); |
241 | $defaultLabels = static::getExpiryOptions( $this->getContext(), false )['options']; |
242 | $localizedExpiry = array_search( $submittedExpiry, $defaultLabels ); |
243 | $expiryLabel = $expiryDays && $localizedExpiry === false |
244 | ? $this->getContext()->msg( 'days', $expiryDays )->text() |
245 | : $localizedExpiry; |
246 | |
247 | // Determine which message to use, depending on whether this is a talk page or not |
248 | // and whether an expiry was selected. |
249 | $isTalk = $this->getTitle()->isTalkPage(); |
250 | if ( wfIsInfinity( $expiry ) ) { |
251 | $msgKey = $isTalk ? 'addedwatchindefinitelytext-talk' : 'addedwatchindefinitelytext'; |
252 | } elseif ( $expiryDays > 0 ) { |
253 | $msgKey = $isTalk ? 'addedwatchexpirytext-talk' : 'addedwatchexpirytext'; |
254 | } elseif ( $expiryDays < 1 ) { |
255 | $msgKey = $isTalk ? 'addedwatchexpiryhours-talk' : 'addedwatchexpiryhours'; |
256 | } |
257 | } |
258 | $this->getOutput()->addWikiMsg( $msgKey, $this->getTitle()->getPrefixedText(), $expiryLabel ); |
259 | } |
260 | |
261 | public function doesWrites() { |
262 | return true; |
263 | } |
264 | } |