Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
81.76% |
139 / 170 |
|
55.56% |
5 / 9 |
CRAP | |
0.00% |
0 / 1 |
ApiEditMassMessageList | |
81.76% |
139 / 170 |
|
55.56% |
5 / 9 |
65.16 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
execute | |
98.00% |
98 / 100 |
|
0.00% |
0 / 1 |
31 | |||
getEditSummary | |
42.86% |
15 / 35 |
|
0.00% |
0 / 1 |
38.87 | |||
getAllowedParams | |
100.00% |
21 / 21 |
|
100.00% |
1 / 1 |
1 | |||
mustBePosted | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
needsToken | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isWriteMode | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getExamplesMessages | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
getHelpUrls | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\MassMessage\Api; |
4 | |
5 | use ApiBase; |
6 | use ApiMain; |
7 | use ApiWatchlistTrait; |
8 | use MediaWiki\MassMessage\Content\MassMessageListContent; |
9 | use MediaWiki\MassMessage\Content\MassMessageListContentHandler; |
10 | use MediaWiki\MediaWikiServices; |
11 | use MediaWiki\Revision\SlotRecord; |
12 | use MediaWiki\Title\Title; |
13 | use Wikimedia\ParamValidator\ParamValidator; |
14 | |
15 | /** |
16 | * API module to edit a MassMessage delivery list. |
17 | * |
18 | * @ingroup API |
19 | */ |
20 | |
21 | class ApiEditMassMessageList extends ApiBase { |
22 | |
23 | use ApiWatchlistTrait; |
24 | |
25 | /** @inheritDoc */ |
26 | public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) { |
27 | parent::__construct( $mainModule, $moduleName, $modulePrefix ); |
28 | |
29 | // Needed for ApiWatchlistTrait. |
30 | $this->watchlistExpiryEnabled = false; |
31 | } |
32 | |
33 | public function execute() { |
34 | $data = $this->extractRequestParams(); |
35 | |
36 | $this->requireAtLeastOneParameter( $data, 'add', 'remove', 'description' ); |
37 | |
38 | $spamlist = Title::newFromText( $data['spamlist'] ); |
39 | if ( $spamlist === null |
40 | || !$spamlist->exists() |
41 | || !$spamlist->hasContentModel( 'MassMessageListContent' ) |
42 | ) { |
43 | $this->dieWithError( 'apierror-massmessage-invalidspamlist', 'invalidspamlist' ); |
44 | } |
45 | '@phan-var Title $spamlist'; |
46 | |
47 | /** |
48 | * @var MassMessageListContent $content |
49 | */ |
50 | $content = MediaWikiServices::getInstance() |
51 | ->getRevisionLookup() |
52 | ->getRevisionByTitle( $spamlist ) |
53 | ->getContent( SlotRecord::MAIN ); |
54 | '@phan-var MassMessageListContent $content'; |
55 | $description = $content->getDescription(); |
56 | $targets = $content->getTargets(); |
57 | |
58 | // Create a copy. |
59 | $newTargets = $targets; |
60 | |
61 | if ( isset( $data['add'] ) ) { |
62 | $invalidAdd = []; |
63 | |
64 | foreach ( $data['add'] as $page ) { |
65 | $target = MassMessageListContentHandler::extractTarget( $page ); |
66 | if ( isset( $target['errors'] ) ) { |
67 | $item = [ '*' => $page ]; |
68 | foreach ( $target['errors'] as $error ) { |
69 | $item[$error] = ''; |
70 | } |
71 | $invalidAdd[] = $item; |
72 | } else { |
73 | $newTargets[] = $target; |
74 | } |
75 | } |
76 | |
77 | // Remove duplicates |
78 | $newTargets = MassMessageListContentHandler::normalizeTargetArray( $newTargets ); |
79 | $invalidAdd = array_unique( $invalidAdd, SORT_REGULAR ); |
80 | } |
81 | |
82 | if ( isset( $data['remove'] ) ) { |
83 | $toRemove = []; |
84 | $invalidRemove = []; |
85 | |
86 | foreach ( $data['remove'] as $page ) { |
87 | $target = MassMessageListContentHandler::extractTarget( $page ); |
88 | if ( isset( $target['errors'] ) || !in_array( $target, $newTargets ) ) { |
89 | $invalidRemove[] = $page; |
90 | } else { |
91 | $toRemove[] = $target; |
92 | } |
93 | } |
94 | |
95 | // In case there are duplicates within the provided list |
96 | $toRemove = MassMessageListContentHandler::normalizeTargetArray( $toRemove ); |
97 | $invalidRemove = array_unique( $invalidRemove ); |
98 | |
99 | $newTargets = array_values( array_udiff( $newTargets, $toRemove, |
100 | [ MassMessageListContentHandler::class, 'compareTargets' ] ) ); |
101 | } |
102 | |
103 | if ( isset( $data['add'] ) ) { |
104 | $added = array_values( array_udiff( $newTargets, $targets, |
105 | [ MassMessageListContentHandler::class, 'compareTargets' ] ) ); |
106 | } else { |
107 | $added = []; |
108 | } |
109 | |
110 | if ( isset( $data['remove'] ) ) { |
111 | $removed = array_values( array_udiff( $targets, $newTargets, |
112 | [ MassMessageListContentHandler::class, 'compareTargets' ] ) ); |
113 | } else { |
114 | $removed = []; |
115 | } |
116 | |
117 | $description = $oldDescription = $content->getDescription(); |
118 | $descriptionChanged = false; |
119 | if ( isset( $data['description'] ) ) { |
120 | $description = $data['description']; |
121 | $descriptionChanged = ( $description !== $oldDescription ); |
122 | } |
123 | |
124 | // Make an edit only if there are added or removed pages, or the description changed |
125 | if ( $added || $removed || $descriptionChanged ) { |
126 | $summary = $this->getEditSummary( $added, $removed, $descriptionChanged ); |
127 | $editResult = MassMessageListContentHandler::edit( |
128 | $spamlist, |
129 | $description, |
130 | $newTargets, |
131 | $summary, |
132 | $this->getPermissionManager()->userHasRight( $this->getUser(), 'minoredit' ) && |
133 | $data['minor'], |
134 | $data['watchlist'], |
135 | // We can pass $this because APIs implement IContextSource |
136 | $this |
137 | ); |
138 | if ( !$editResult->isGood() ) { |
139 | $this->dieStatus( $editResult ); |
140 | } |
141 | } |
142 | |
143 | $result = $this->getResult(); |
144 | $resultArray = [ 'result' => 'Success' ]; |
145 | |
146 | if ( isset( $data['add'] ) ) { |
147 | $resultArray['added'] = $added; |
148 | |
149 | // Use a LinkBatch to look up and cache existence for all local targets |
150 | $lb = MediaWikiServices::getInstance()->getLinkBatchFactory()->newLinkBatch(); |
151 | foreach ( $resultArray['added'] as $target ) { |
152 | if ( !isset( $target['site'] ) ) { |
153 | $lb->addObj( Title::newFromText( $target['title'] ) ); |
154 | } |
155 | } |
156 | $lb->execute(); |
157 | |
158 | // Add an empty "missing" attribute to new local targets that do not exist |
159 | foreach ( $resultArray['added'] as &$target ) { |
160 | if ( !isset( $target['site'] ) |
161 | && !Title::newFromText( $target['title'] )->exists() |
162 | ) { |
163 | $target['missing'] = ''; |
164 | } |
165 | } |
166 | |
167 | $result->setIndexedTagName( $resultArray['added'], 'page' ); |
168 | |
169 | if ( !empty( $invalidAdd ) ) { |
170 | $resultArray['result'] = 'Done'; |
171 | $resultArray['invalidadd'] = $invalidAdd; |
172 | $result->setIndexedTagName( $resultArray['invalidadd'], 'item' ); |
173 | } |
174 | } |
175 | |
176 | if ( isset( $data['remove'] ) ) { |
177 | $resultArray['removed'] = $removed; |
178 | $result->setIndexedTagName( $resultArray['removed'], 'page' ); |
179 | |
180 | if ( !empty( $invalidRemove ) ) { |
181 | $resultArray['result'] = 'Done'; |
182 | $resultArray['invalidremove'] = $invalidRemove; |
183 | $result->setIndexedTagName( $resultArray['invalidremove'], 'item' ); |
184 | } |
185 | } |
186 | |
187 | if ( isset( $data['description'] ) ) { |
188 | if ( $descriptionChanged ) { |
189 | $resultArray['description'] = $description; |
190 | } else { |
191 | $resultArray['result'] = 'Done'; |
192 | $resultArray['invaliddescription'] = $description; |
193 | } |
194 | } |
195 | |
196 | $result->addValue( |
197 | null, |
198 | $this->getModuleName(), |
199 | $resultArray |
200 | ); |
201 | } |
202 | |
203 | /** |
204 | * Get the edit summary. |
205 | * @todo add the actual new description to the summary, rather than noting that it changed |
206 | * |
207 | * @param array $added |
208 | * @param array $removed |
209 | * @param bool $descriptionChanged |
210 | * @return string |
211 | */ |
212 | protected function getEditSummary( $added, $removed, $descriptionChanged ) { |
213 | $msgChange = ( $descriptionChanged ? 'change' : '' ); |
214 | if ( $added && $removed ) { |
215 | // * massmessage-summary-addremove |
216 | // * massmessage-summary-addremovechange |
217 | $summaryMsg = $this->msg( 'massmessage-summary-addremove' . $msgChange ) |
218 | ->numParams( count( $added ) ) |
219 | ->numParams( count( $removed ) ); |
220 | } elseif ( $added && !$removed ) { |
221 | if ( count( $added ) === 1 ) { |
222 | if ( isset( $added[0]['site'] ) ) { |
223 | // * massmessage-summary-addonsite |
224 | // * massmessage-summary-addonsitechange |
225 | $summaryMsg = $this->msg( |
226 | 'massmessage-summary-addonsite' . $msgChange, |
227 | $added[0]['title'], |
228 | $added[0]['site'] |
229 | ); |
230 | } else { |
231 | // * massmessage-summary-add |
232 | // * massmessage-summary-addchange |
233 | $summaryMsg = $this->msg( |
234 | 'massmessage-summary-add' . $msgChange, |
235 | $added[0]['title'] |
236 | ); |
237 | } |
238 | } else { |
239 | // * massmessage-summary-addmulti |
240 | // * massmessage-summary-addmultichange |
241 | $summaryMsg = $this->msg( 'massmessage-summary-addmulti' . $msgChange ) |
242 | ->numParams( count( $added ) ); |
243 | } |
244 | } elseif ( !$added && $removed ) { |
245 | if ( count( $removed ) === 1 ) { |
246 | if ( isset( $removed[0]['site'] ) ) { |
247 | // * massmessage-summary-removeonsite |
248 | // * massmessage-summary-removeonsitechange |
249 | $summaryMsg = $this->msg( |
250 | 'massmessage-summary-removeonsite' . $msgChange, |
251 | $removed[0]['title'], |
252 | $removed[0]['site'] |
253 | ); |
254 | } else { |
255 | // * massmessage-summary-remove |
256 | // * massmessage-summary-removechange |
257 | $summaryMsg = $this->msg( |
258 | 'massmessage-summary-remove' . $msgChange, |
259 | $removed[0]['title'] |
260 | ); |
261 | } |
262 | } else { |
263 | // * massmessage-summary-removemulti |
264 | // * massmessage-summary-removemultichange |
265 | $summaryMsg = $this->msg( 'massmessage-summary-removemulti' . $msgChange ) |
266 | ->numParams( count( $removed ) ); |
267 | } |
268 | } else { |
269 | $summaryMsg = $this->msg( 'massmessage-summary-change' ); |
270 | } |
271 | return $summaryMsg->inContentLanguage()->text(); |
272 | } |
273 | |
274 | /** @inheritDoc */ |
275 | public function getAllowedParams() { |
276 | return [ |
277 | 'spamlist' => [ |
278 | ParamValidator::PARAM_TYPE => 'string', |
279 | ParamValidator::PARAM_REQUIRED => true |
280 | ], |
281 | 'description' => [ |
282 | ParamValidator::PARAM_TYPE => 'string' |
283 | ], |
284 | 'add' => [ |
285 | ParamValidator::PARAM_TYPE => 'string', |
286 | ParamValidator::PARAM_ISMULTI => true |
287 | ], |
288 | 'remove' => [ |
289 | ParamValidator::PARAM_TYPE => 'string', |
290 | ParamValidator::PARAM_ISMULTI => true |
291 | ], |
292 | 'minor' => [ |
293 | ParamValidator::PARAM_TYPE => 'boolean', |
294 | ParamValidator::PARAM_DEFAULT => false |
295 | ], |
296 | ] + $this->getWatchlistParams() + [ 'token' => null ]; |
297 | } |
298 | |
299 | /** @inheritDoc */ |
300 | public function mustBePosted() { |
301 | return true; |
302 | } |
303 | |
304 | /** @inheritDoc */ |
305 | public function needsToken() { |
306 | return 'csrf'; |
307 | } |
308 | |
309 | /** @inheritDoc */ |
310 | public function isWriteMode() { |
311 | return true; |
312 | } |
313 | |
314 | /** @inheritDoc */ |
315 | protected function getExamplesMessages() { |
316 | return [ |
317 | 'action=editmassmessagelist&spamlist=Example&add=User%20talk%3AFoo%7CTalk%3ABar' . |
318 | '&remove=Talk%3ABaz&token=TOKEN' |
319 | => 'apihelp-editmassmessagelist-example-1', |
320 | 'action=editmassmessagelist&spamlist=Example' . |
321 | '&description=FooBor%20delivery%20services&token=TOKEN' |
322 | => 'apihelp-editmassmessagelist-example-2' |
323 | ]; |
324 | } |
325 | |
326 | /** @inheritDoc */ |
327 | public function getHelpUrls() { |
328 | return [ 'https://www.mediawiki.org/wiki/Extension:MassMessage/API' ]; |
329 | } |
330 | |
331 | } |