Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 175 |
|
0.00% |
0 / 12 |
CRAP | |
0.00% |
0 / 1 |
SpecialEditMassMessageList | |
0.00% |
0 / 175 |
|
0.00% |
0 / 12 |
2756 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
doesWrites | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setParameter | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
156 | |||
setHeaders | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
72 | |||
getFormFields | |
0.00% |
0 / 55 |
|
0.00% |
0 / 1 |
42 | |||
alterForm | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
preHtml | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
42 | |||
onSubmit | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
90 | |||
onSuccess | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
parseInput | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
20 | |||
isListed | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDisplayFormat | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\MassMessage\Specials; |
4 | |
5 | use LogEventsList; |
6 | use MediaWiki\CommentStore\CommentStore; |
7 | use MediaWiki\EditPage\EditPage; |
8 | use MediaWiki\Html\Html; |
9 | use MediaWiki\HTMLForm\HTMLForm; |
10 | use MediaWiki\MassMessage\Content\MassMessageListContent; |
11 | use MediaWiki\MassMessage\Content\MassMessageListContentHandler; |
12 | use MediaWiki\MassMessage\Lookup\DatabaseLookup; |
13 | use MediaWiki\Permissions\PermissionManager; |
14 | use MediaWiki\Permissions\RestrictionStore; |
15 | use MediaWiki\Revision\RevisionLookup; |
16 | use MediaWiki\Revision\RevisionRecord; |
17 | use MediaWiki\Revision\SlotRecord; |
18 | use MediaWiki\SpecialPage\FormSpecialPage; |
19 | use MediaWiki\Status\Status; |
20 | use MediaWiki\Title\Title; |
21 | use MediaWiki\User\Options\UserOptionsLookup; |
22 | use MediaWiki\Watchlist\WatchlistManager; |
23 | use Wikimedia\Rdbms\IDBAccessObject; |
24 | |
25 | class SpecialEditMassMessageList extends FormSpecialPage { |
26 | |
27 | /** |
28 | * The title of the list to edit |
29 | * If not null, the title refers to a delivery list. |
30 | * |
31 | * @var Title|null |
32 | */ |
33 | protected $title; |
34 | |
35 | /** |
36 | * The revision to edit |
37 | * If not null, the user can edit the delivery list. |
38 | * |
39 | * @var RevisionRecord|null |
40 | */ |
41 | protected $rev; |
42 | |
43 | /** |
44 | * The message key for the error encountered while parsing the title, if any |
45 | * |
46 | * @var string|null |
47 | */ |
48 | protected $errorMsgKey; |
49 | |
50 | /** |
51 | * Provides access to user options |
52 | * |
53 | * @var UserOptionsLookup |
54 | */ |
55 | private $userOptionsLookup; |
56 | |
57 | /** @var RestrictionStore */ |
58 | private $restrictionStore; |
59 | |
60 | /** @var WatchlistManager */ |
61 | private $watchlistManager; |
62 | |
63 | /** @var PermissionManager */ |
64 | private $permissionManager; |
65 | |
66 | /** @var RevisionLookup */ |
67 | private $revisionLookup; |
68 | |
69 | /** |
70 | * @param UserOptionsLookup $userOptionsLookup |
71 | * @param RestrictionStore $restrictionStore |
72 | * @param WatchlistManager $watchlistManager |
73 | * @param PermissionManager $permissionManager |
74 | * @param RevisionLookup $revisionLookup |
75 | */ |
76 | public function __construct( |
77 | UserOptionsLookup $userOptionsLookup, |
78 | RestrictionStore $restrictionStore, |
79 | WatchlistManager $watchlistManager, |
80 | PermissionManager $permissionManager, |
81 | RevisionLookup $revisionLookup |
82 | ) { |
83 | parent::__construct( 'EditMassMessageList' ); |
84 | |
85 | $this->userOptionsLookup = $userOptionsLookup; |
86 | $this->restrictionStore = $restrictionStore; |
87 | $this->watchlistManager = $watchlistManager; |
88 | $this->permissionManager = $permissionManager; |
89 | $this->revisionLookup = $revisionLookup; |
90 | } |
91 | |
92 | public function doesWrites() { |
93 | return true; |
94 | } |
95 | |
96 | /** |
97 | * @param string $par |
98 | */ |
99 | protected function setParameter( $par ) { |
100 | if ( $par === null || $par === '' ) { |
101 | $this->errorMsgKey = 'massmessage-edit-invalidtitle'; |
102 | } else { |
103 | $title = Title::newFromText( $par ); |
104 | |
105 | if ( !$title |
106 | || !$title->exists() |
107 | || !$title->hasContentModel( 'MassMessageListContent' ) |
108 | ) { |
109 | $this->errorMsgKey = 'massmessage-edit-invalidtitle'; |
110 | } else { |
111 | $this->title = $title; |
112 | if ( !$this->permissionManager->userCan( 'edit', |
113 | $this->getUser(), $title ) |
114 | ) { |
115 | $this->errorMsgKey = 'massmessage-edit-nopermission'; |
116 | } else { |
117 | $revId = $this->getRequest()->getInt( 'oldid' ); |
118 | if ( $revId > 0 ) { |
119 | $rev = $this->revisionLookup->getRevisionById( $revId ); |
120 | if ( $rev |
121 | && $title->equals( $rev->getPageAsLinkTarget() ) |
122 | && $rev->getSlot( SlotRecord::MAIN, RevisionRecord::RAW ) |
123 | ->getModel() === 'MassMessageListContent' |
124 | && RevisionRecord::userCanBitfield( |
125 | $rev->getVisibility(), |
126 | RevisionRecord::DELETED_TEXT, |
127 | $this->getUser() |
128 | ) |
129 | ) { |
130 | $this->rev = $rev; |
131 | } else { |
132 | // Use the latest revision for the title if $rev is invalid. |
133 | $this->rev = $this->revisionLookup->getRevisionByTitle( $title ); |
134 | } |
135 | } else { |
136 | $this->rev = $this->revisionLookup->getRevisionByTitle( $title ); |
137 | } |
138 | } |
139 | } |
140 | } |
141 | } |
142 | |
143 | /** |
144 | * Override the parent implementation to modify the page title and add a backlink. |
145 | */ |
146 | public function setHeaders() { |
147 | parent::setHeaders(); |
148 | if ( $this->title ) { |
149 | $out = $this->getOutput(); |
150 | |
151 | // Page title |
152 | $out->setPageTitleMsg( |
153 | $this->msg( 'massmessage-edit-pagetitle', $this->title->getPrefixedText() ) |
154 | ); |
155 | |
156 | // Backlink |
157 | if ( $this->rev ) { |
158 | $revId = $this->rev->getId(); |
159 | $query = ( $revId !== $this->title->getLatestRevId() ) ? |
160 | [ 'oldid' => $revId ] : []; |
161 | } else { |
162 | $query = []; |
163 | } |
164 | $out->addBacklinkSubtitle( $this->title, $query ); |
165 | |
166 | // Edit notices; modified from EditPage::showHeader() |
167 | if ( $this->rev ) { |
168 | $out->addHTML( |
169 | implode( "\n", $this->title->getEditNotices( $this->rev->getId() ) ) |
170 | ); |
171 | } |
172 | |
173 | // Protection warnings; modified from EditPage::showHeader() |
174 | if ( $this->restrictionStore->isProtected( $this->title, 'edit' ) |
175 | && $this->permissionManager |
176 | ->getNamespaceRestrictionLevels( $this->title->getNamespace() ) !== [ '' ] |
177 | ) { |
178 | if ( $this->restrictionStore->isSemiProtected( $this->title ) ) { |
179 | $noticeMsg = 'semiprotectedpagewarning'; |
180 | } else { |
181 | // Full protection |
182 | $noticeMsg = 'protectedpagewarning'; |
183 | } |
184 | LogEventsList::showLogExtract( $out, 'protect', $this->title, '', |
185 | [ 'lim' => 1, 'msgKey' => [ $noticeMsg ] ] ); |
186 | } |
187 | } |
188 | } |
189 | |
190 | /** |
191 | * @return array |
192 | */ |
193 | protected function getFormFields() { |
194 | // Return an empty form if the title is invalid or if the user can't edit the list. |
195 | if ( !$this->rev ) { |
196 | return []; |
197 | } |
198 | |
199 | $this->getOutput()->addModules( [ 'ext.MassMessage.edit', 'ext.MassMessage.styles' ] ); |
200 | |
201 | /** |
202 | * @var MassMessageListContent $content |
203 | */ |
204 | $content = $this->rev->getContent( |
205 | SlotRecord::MAIN, |
206 | RevisionRecord::FOR_THIS_USER, |
207 | $this->getUser() |
208 | ); |
209 | '@phan-var MassMessageListContent $content'; |
210 | $description = $content->getDescription(); |
211 | $targets = $content->getTargetStrings(); |
212 | |
213 | $fields = [ |
214 | 'description' => [ |
215 | 'type' => 'textarea', |
216 | 'rows' => 5, |
217 | 'default' => $description ?? '', |
218 | 'useeditfont' => true, |
219 | 'label-message' => 'massmessage-edit-description', |
220 | ], |
221 | 'content' => [ |
222 | 'type' => 'textarea', |
223 | 'default' => ( $targets !== null ) ? implode( "\n", $targets ) : '', |
224 | 'label-message' => 'massmessage-edit-content', |
225 | ], |
226 | 'summary' => [ |
227 | 'type' => 'text', |
228 | 'maxlength' => CommentStore::COMMENT_CHARACTER_LIMIT, |
229 | 'size' => 60, |
230 | 'label-message' => 'summary', |
231 | ], |
232 | ]; |
233 | |
234 | if ( $this->permissionManager->userHasRight( $this->getUser(), 'minoredit' ) ) { |
235 | $fields['minor'] = [ |
236 | 'name' => 'minor', |
237 | 'id' => 'wpMinoredit', |
238 | 'type' => 'check', |
239 | 'label-message' => 'minoredit', |
240 | 'default' => false, |
241 | ]; |
242 | } |
243 | |
244 | if ( $this->getUser()->isNamed() ) { |
245 | $fields['watch'] = [ |
246 | 'name' => 'watch', |
247 | 'id' => 'wpWatchthis', |
248 | 'type' => 'check', |
249 | 'label-message' => 'watchthis', |
250 | 'default' => $this->watchlistManager->isWatched( $this->getUser(), $this->title ) || |
251 | $this->userOptionsLookup->getOption( $this->getUser(), 'watchdefault' ), |
252 | ]; |
253 | } |
254 | |
255 | return $fields + [ |
256 | 'copyright' => [ |
257 | 'type' => 'info', |
258 | 'default' => EditPage::getCopyrightWarning( $this->title, 'parse', $this ), |
259 | 'raw' => true, |
260 | ], |
261 | ]; |
262 | } |
263 | |
264 | /** |
265 | * Hide the form if the title is invalid or if the user can't edit the list. If neither |
266 | * of these are true, then add a cancel button alongside the automatic save button. Also |
267 | * add an ID to the form for targeting with CSS styles. |
268 | * |
269 | * @param HTMLForm $form |
270 | */ |
271 | protected function alterForm( HTMLForm $form ) { |
272 | if ( !$this->rev ) { |
273 | $form->setWrapperLegend( false ); |
274 | $form->suppressDefaultSubmit(); |
275 | } else { |
276 | $form->showCancel(); |
277 | $form->setCancelTarget( $this->title ); |
278 | } |
279 | $form->setId( 'mw-massmessage-edit-form' ); |
280 | } |
281 | |
282 | /** |
283 | * Return instructions for the form and / or warnings. |
284 | * |
285 | * @return string |
286 | */ |
287 | protected function preHtml() { |
288 | $allowGlobalMessaging = $this->getConfig()->get( 'AllowGlobalMessaging' ); |
289 | |
290 | if ( $this->rev ) { |
291 | // Instructions |
292 | if ( $allowGlobalMessaging && count( DatabaseLookup::getDatabases() ) > 1 ) { |
293 | $headerKey = 'massmessage-edit-headermulti'; |
294 | } else { |
295 | $headerKey = 'massmessage-edit-header'; |
296 | } |
297 | $html = $this->msg( $headerKey )->parseAsBlock(); |
298 | |
299 | // Deleted revision warning |
300 | if ( $this->rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) { |
301 | $html .= Html::openElement( 'div', [ 'class' => 'mw-warning plainlinks' ] ); |
302 | $html .= $this->msg( 'rev-deleted-text-view' )->parseAsBlock(); |
303 | $html .= Html::closeElement( 'div' ); |
304 | } |
305 | |
306 | // Old revision warning |
307 | if ( $this->rev->getId() !== $this->title->getLatestRevID( IDBAccessObject::READ_LATEST ) ) { |
308 | $html .= $this->msg( 'editingold' )->parseAsBlock(); |
309 | } |
310 | } else { |
311 | // Error determined in setParameter() |
312 | $html = $this->msg( $this->errorMsgKey )->parseAsBlock(); |
313 | } |
314 | return $html; |
315 | } |
316 | |
317 | /** |
318 | * @param array $data |
319 | * @param HTMLForm|null $form |
320 | * @return Status |
321 | */ |
322 | public function onSubmit( array $data, ?HTMLForm $form = null ) { |
323 | if ( !$this->title ) { |
324 | return Status::newFatal( 'massmessage-edit-invalidtitle' ); |
325 | } |
326 | |
327 | // Parse input into target array. |
328 | $parseResult = self::parseInput( $data['content'] ); |
329 | if ( !$parseResult->isGood() ) { |
330 | // Wikitext list of escaped invalid target strings |
331 | $invalidList = '* ' . implode( "\n* ", array_map( 'wfEscapeWikiText', |
332 | $parseResult->value ) ); |
333 | return Status::newFatal( $this->msg( 'massmessage-edit-invalidtargets', |
334 | count( $parseResult->value ), $invalidList ) ); |
335 | } |
336 | |
337 | // Blank edit summary warning |
338 | if ( $data['summary'] === '' |
339 | && $this->userOptionsLookup->getOption( $this->getUser(), 'forceeditsummary' ) |
340 | && !$this->getRequest()->getCheck( 'summarywarned' ) |
341 | ) { |
342 | $form->addHiddenField( 'summarywarned', 'true' ); |
343 | return Status::newFatal( $this->msg( 'massmessage-edit-missingsummary' ) ); |
344 | } |
345 | |
346 | $editResult = MassMessageListContentHandler::edit( |
347 | $this->title, |
348 | $data['description'], |
349 | $parseResult->value, |
350 | $data['summary'], |
351 | $this->permissionManager->userHasRight( $this->getUser(), 'minoredit' ) && $data['minor'], |
352 | $data['watch'] ? 'watch' : 'unwatch', |
353 | $this->getContext() |
354 | ); |
355 | |
356 | if ( !$editResult->isGood() ) { |
357 | return $editResult; |
358 | } |
359 | |
360 | $this->getOutput()->redirect( $this->title->getFullURL() ); |
361 | return Status::newGood(); |
362 | } |
363 | |
364 | public function onSuccess() { |
365 | // No-op: We have already redirected. |
366 | } |
367 | |
368 | /** |
369 | * Parse user input into an array of targets and return it as the value of a Status object. |
370 | * If input contains invalid data, the value is the array of invalid target strings. |
371 | * |
372 | * @param string $input |
373 | * @return Status |
374 | */ |
375 | protected static function parseInput( $input ) { |
376 | // Array of non-empty lines |
377 | $lines = array_filter( explode( "\n", $input ), 'trim' ); |
378 | |
379 | $targets = []; |
380 | $invalidTargets = []; |
381 | foreach ( $lines as $line ) { |
382 | $target = MassMessageListContentHandler::extractTarget( $line ); |
383 | if ( array_key_exists( 'errors', $target ) ) { |
384 | $invalidTargets[] = $line; |
385 | } |
386 | $targets[] = $target; |
387 | } |
388 | |
389 | $result = new Status; |
390 | if ( !$invalidTargets ) { |
391 | $result->setResult( true, |
392 | MassMessageListContentHandler::normalizeTargetArray( $targets ) ); |
393 | } else { |
394 | $result->setResult( false, $invalidTargets ); |
395 | } |
396 | return $result; |
397 | } |
398 | |
399 | /** |
400 | * @return bool |
401 | */ |
402 | public function isListed() { |
403 | return false; |
404 | } |
405 | |
406 | /** |
407 | * @return string |
408 | */ |
409 | protected function getDisplayFormat() { |
410 | return 'ooui'; |
411 | } |
412 | } |