Remove Revision::getRevisionText from ApiQueryDeletedrevs
[lhc/web/wiklou.git] / includes / api / ApiComparePages.php
1 <?php
2 /**
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 * http://www.gnu.org/copyleft/gpl.html
18 *
19 * @file
20 */
21
22 use MediaWiki\MediaWikiServices;
23 use MediaWiki\Revision\MutableRevisionRecord;
24 use MediaWiki\Revision\RevisionRecord;
25 use MediaWiki\Revision\RevisionArchiveRecord;
26 use MediaWiki\Revision\RevisionStore;
27 use MediaWiki\Revision\SlotRecord;
28
29 /**
30 * @ingroup API
31 */
32 class ApiComparePages extends ApiBase {
33
34 /** @var RevisionStore */
35 private $revisionStore;
36
37 /** @var \MediaWiki\Revision\SlotRoleRegistry */
38 private $slotRoleRegistry;
39
40 private $guessedTitle = false, $props;
41
42 public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
43 parent::__construct( $mainModule, $moduleName, $modulePrefix );
44 $this->revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
45 $this->slotRoleRegistry = MediaWikiServices::getInstance()->getSlotRoleRegistry();
46 }
47
48 public function execute() {
49 $params = $this->extractRequestParams();
50
51 // Parameter validation
52 $this->requireAtLeastOneParameter(
53 $params, 'fromtitle', 'fromid', 'fromrev', 'fromtext', 'fromslots'
54 );
55 $this->requireAtLeastOneParameter(
56 $params, 'totitle', 'toid', 'torev', 'totext', 'torelative', 'toslots'
57 );
58
59 $this->props = array_flip( $params['prop'] );
60
61 // Cache responses publicly by default. This may be overridden later.
62 $this->getMain()->setCacheMode( 'public' );
63
64 // Get the 'from' RevisionRecord
65 list( $fromRev, $fromRelRev, $fromValsRev ) = $this->getDiffRevision( 'from', $params );
66
67 // Get the 'to' RevisionRecord
68 if ( $params['torelative'] !== null ) {
69 if ( !$fromRelRev ) {
70 $this->dieWithError( 'apierror-compare-relative-to-nothing' );
71 }
72 if ( $params['torelative'] !== 'cur' && $fromRelRev instanceof RevisionArchiveRecord ) {
73 // RevisionStore's getPreviousRevision/getNextRevision blow up
74 // when passed an RevisionArchiveRecord for a deleted page
75 $this->dieWithError( [ 'apierror-compare-relative-to-deleted', $params['torelative'] ] );
76 }
77 switch ( $params['torelative'] ) {
78 case 'prev':
79 // Swap 'from' and 'to'
80 list( $toRev, $toRelRev, $toValsRev ) = [ $fromRev, $fromRelRev, $fromValsRev ];
81 $fromRev = $this->revisionStore->getPreviousRevision( $toRelRev );
82 $fromRelRev = $fromRev;
83 $fromValsRev = $fromRev;
84 if ( !$fromRev ) {
85 $title = Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() );
86 $this->addWarning( [
87 'apiwarn-compare-no-prev',
88 wfEscapeWikiText( $title->getPrefixedText() ),
89 $toRelRev->getId()
90 ] );
91
92 // (T203433) Create an empty dummy revision as the "previous".
93 // The main slot has to exist, the rest will be handled by DifferenceEngine.
94 $fromRev = $this->revisionStore->newMutableRevisionFromArray( [
95 'title' => $title ?: Title::makeTitle( NS_SPECIAL, 'Badtitle/' . __METHOD__ )
96 ] );
97 $fromRev->setContent(
98 SlotRecord::MAIN,
99 $toRelRev->getContent( SlotRecord::MAIN, RevisionRecord::RAW )
100 ->getContentHandler()
101 ->makeEmptyContent()
102 );
103 }
104 break;
105
106 case 'next':
107 $toRev = $this->revisionStore->getNextRevision( $fromRelRev );
108 $toRelRev = $toRev;
109 $toValsRev = $toRev;
110 if ( !$toRev ) {
111 $title = Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() );
112 $this->addWarning( [
113 'apiwarn-compare-no-next',
114 wfEscapeWikiText( $title->getPrefixedText() ),
115 $fromRelRev->getId()
116 ] );
117
118 // (T203433) The web UI treats "next" as "cur" in this case.
119 // Avoid repeating metadata by making a MutableRevisionRecord with no changes.
120 $toRev = MutableRevisionRecord::newFromParentRevision( $fromRelRev );
121 }
122 break;
123
124 case 'cur':
125 $title = $fromRelRev->getPageAsLinkTarget();
126 $toRev = $this->revisionStore->getRevisionByTitle( $title );
127 if ( !$toRev ) {
128 $title = Title::newFromLinkTarget( $title );
129 $this->dieWithError(
130 [ 'apierror-missingrev-title', wfEscapeWikiText( $title->getPrefixedText() ) ], 'nosuchrevid'
131 );
132 }
133 $toRelRev = $toRev;
134 $toValsRev = $toRev;
135 break;
136 }
137 } else {
138 list( $toRev, $toRelRev, $toValsRev ) = $this->getDiffRevision( 'to', $params );
139 }
140
141 // Handle missing from or to revisions (should never happen)
142 // @codeCoverageIgnoreStart
143 if ( !$fromRev || !$toRev ) {
144 $this->dieWithError( 'apierror-baddiff' );
145 }
146 // @codeCoverageIgnoreEnd
147
148 // Handle revdel
149 if ( !$fromRev->audienceCan(
150 RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_THIS_USER, $this->getUser()
151 ) ) {
152 $this->dieWithError( [ 'apierror-missingcontent-revid', $fromRev->getId() ], 'missingcontent' );
153 }
154 if ( !$toRev->audienceCan(
155 RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_THIS_USER, $this->getUser()
156 ) ) {
157 $this->dieWithError( [ 'apierror-missingcontent-revid', $toRev->getId() ], 'missingcontent' );
158 }
159
160 // Get the diff
161 $context = new DerivativeContext( $this->getContext() );
162 if ( $fromRelRev && $fromRelRev->getPageAsLinkTarget() ) {
163 $context->setTitle( Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() ) );
164 } elseif ( $toRelRev && $toRelRev->getPageAsLinkTarget() ) {
165 $context->setTitle( Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() ) );
166 } else {
167 $guessedTitle = $this->guessTitle();
168 if ( $guessedTitle ) {
169 $context->setTitle( $guessedTitle );
170 }
171 }
172 $de = new DifferenceEngine( $context );
173 $de->setRevisions( $fromRev, $toRev );
174 if ( $params['slots'] === null ) {
175 $difftext = $de->getDiffBody();
176 if ( $difftext === false ) {
177 $this->dieWithError( 'apierror-baddiff' );
178 }
179 } else {
180 $difftext = [];
181 foreach ( $params['slots'] as $role ) {
182 $difftext[$role] = $de->getDiffBodyForRole( $role );
183 }
184 }
185
186 // Fill in the response
187 $vals = [];
188 $this->setVals( $vals, 'from', $fromValsRev );
189 $this->setVals( $vals, 'to', $toValsRev );
190
191 if ( isset( $this->props['rel'] ) ) {
192 if ( !$fromRev instanceof MutableRevisionRecord ) {
193 $rev = $this->revisionStore->getPreviousRevision( $fromRev );
194 if ( $rev ) {
195 $vals['prev'] = $rev->getId();
196 }
197 }
198 if ( !$toRev instanceof MutableRevisionRecord ) {
199 $rev = $this->revisionStore->getNextRevision( $toRev );
200 if ( $rev ) {
201 $vals['next'] = $rev->getId();
202 }
203 }
204 }
205
206 if ( isset( $this->props['diffsize'] ) ) {
207 $vals['diffsize'] = 0;
208 foreach ( (array)$difftext as $text ) {
209 $vals['diffsize'] += strlen( $text );
210 }
211 }
212 if ( isset( $this->props['diff'] ) ) {
213 if ( is_array( $difftext ) ) {
214 ApiResult::setArrayType( $difftext, 'kvp', 'diff' );
215 $vals['bodies'] = $difftext;
216 } else {
217 ApiResult::setContentValue( $vals, 'body', $difftext );
218 }
219 }
220
221 // Diffs can be really big and there's little point in having
222 // ApiResult truncate it to an empty response since the diff is the
223 // whole reason this module exists. So pass NO_SIZE_CHECK here.
224 $this->getResult()->addValue( null, $this->getModuleName(), $vals, ApiResult::NO_SIZE_CHECK );
225 }
226
227 /**
228 * Load a revision by ID
229 *
230 * Falls back to checking the archive table if appropriate.
231 *
232 * @param int $id
233 * @return RevisionRecord|null
234 */
235 private function getRevisionById( $id ) {
236 $rev = $this->revisionStore->getRevisionById( $id );
237 if ( !$rev && $this->getPermissionManager()
238 ->userHasAnyRight( $this->getUser(), 'deletedtext', 'undelete' )
239 ) {
240 // Try the 'archive' table
241 $arQuery = $this->revisionStore->getArchiveQueryInfo();
242 $row = $this->getDB()->selectRow(
243 $arQuery['tables'],
244 array_merge(
245 $arQuery['fields'],
246 [ 'ar_namespace', 'ar_title' ]
247 ),
248 [ 'ar_rev_id' => $id ],
249 __METHOD__,
250 [],
251 $arQuery['joins']
252 );
253 if ( $row ) {
254 $rev = $this->revisionStore->newRevisionFromArchiveRow( $row );
255 // @phan-suppress-next-line PhanUndeclaredProperty
256 $rev->isArchive = true;
257 }
258 }
259 return $rev;
260 }
261
262 /**
263 * Guess an appropriate default Title for this request
264 *
265 * @return Title|null
266 */
267 private function guessTitle() {
268 if ( $this->guessedTitle !== false ) {
269 return $this->guessedTitle;
270 }
271
272 $this->guessedTitle = null;
273 $params = $this->extractRequestParams();
274
275 foreach ( [ 'from', 'to' ] as $prefix ) {
276 if ( $params["{$prefix}rev"] !== null ) {
277 $rev = $this->getRevisionById( $params["{$prefix}rev"] );
278 if ( $rev ) {
279 $this->guessedTitle = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
280 break;
281 }
282 }
283
284 if ( $params["{$prefix}title"] !== null ) {
285 $title = Title::newFromText( $params["{$prefix}title"] );
286 if ( $title && !$title->isExternal() ) {
287 $this->guessedTitle = $title;
288 break;
289 }
290 }
291
292 if ( $params["{$prefix}id"] !== null ) {
293 $title = Title::newFromID( $params["{$prefix}id"] );
294 if ( $title ) {
295 $this->guessedTitle = $title;
296 break;
297 }
298 }
299 }
300
301 return $this->guessedTitle;
302 }
303
304 /**
305 * Guess an appropriate default content model for this request
306 * @param string $role Slot for which to guess the model
307 * @return string|null Guessed content model
308 */
309 private function guessModel( $role ) {
310 $params = $this->extractRequestParams();
311
312 $title = null;
313 foreach ( [ 'from', 'to' ] as $prefix ) {
314 if ( $params["{$prefix}rev"] !== null ) {
315 $rev = $this->getRevisionById( $params["{$prefix}rev"] );
316 if ( $rev && $rev->hasSlot( $role ) ) {
317 return $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
318 }
319 }
320 }
321
322 $guessedTitle = $this->guessTitle();
323 if ( $guessedTitle ) {
324 return $this->slotRoleRegistry->getRoleHandler( $role )->getDefaultModel( $guessedTitle );
325 }
326
327 if ( isset( $params["fromcontentmodel-$role"] ) ) {
328 return $params["fromcontentmodel-$role"];
329 }
330 if ( isset( $params["tocontentmodel-$role"] ) ) {
331 return $params["tocontentmodel-$role"];
332 }
333
334 if ( $role === SlotRecord::MAIN ) {
335 if ( isset( $params['fromcontentmodel'] ) ) {
336 return $params['fromcontentmodel'];
337 }
338 if ( isset( $params['tocontentmodel'] ) ) {
339 return $params['tocontentmodel'];
340 }
341 }
342
343 return null;
344 }
345
346 /**
347 * Get the RevisionRecord for one side of the diff
348 *
349 * This uses the appropriate set of parameters to determine what content
350 * should be diffed.
351 *
352 * Returns three values:
353 * - A RevisionRecord holding the content
354 * - The revision specified, if any, even if content was supplied
355 * - The revision to pass to setVals(), if any
356 *
357 * @param string $prefix 'from' or 'to'
358 * @param array $params
359 * @return array [ RevisionRecord|null, RevisionRecord|null, RevisionRecord|null ]
360 */
361 private function getDiffRevision( $prefix, array $params ) {
362 // Back compat params
363 $this->requireMaxOneParameter( $params, "{$prefix}text", "{$prefix}slots" );
364 $this->requireMaxOneParameter( $params, "{$prefix}section", "{$prefix}slots" );
365 if ( $params["{$prefix}text"] !== null ) {
366 $params["{$prefix}slots"] = [ SlotRecord::MAIN ];
367 $params["{$prefix}text-main"] = $params["{$prefix}text"];
368 $params["{$prefix}section-main"] = null;
369 $params["{$prefix}contentmodel-main"] = $params["{$prefix}contentmodel"];
370 $params["{$prefix}contentformat-main"] = $params["{$prefix}contentformat"];
371 }
372
373 $title = null;
374 $rev = null;
375 $suppliedContent = $params["{$prefix}slots"] !== null;
376
377 // Get the revision and title, if applicable
378 $revId = null;
379 if ( $params["{$prefix}rev"] !== null ) {
380 $revId = $params["{$prefix}rev"];
381 } elseif ( $params["{$prefix}title"] !== null || $params["{$prefix}id"] !== null ) {
382 if ( $params["{$prefix}title"] !== null ) {
383 $title = Title::newFromText( $params["{$prefix}title"] );
384 if ( !$title || $title->isExternal() ) {
385 $this->dieWithError(
386 [ 'apierror-invalidtitle', wfEscapeWikiText( $params["{$prefix}title"] ) ]
387 );
388 }
389 } else {
390 $title = Title::newFromID( $params["{$prefix}id"] );
391 if ( !$title ) {
392 $this->dieWithError( [ 'apierror-nosuchpageid', $params["{$prefix}id"] ] );
393 }
394 }
395 $revId = $title->getLatestRevID();
396 if ( !$revId ) {
397 $revId = null;
398 // Only die here if we're not using supplied text
399 if ( !$suppliedContent ) {
400 if ( $title->exists() ) {
401 $this->dieWithError(
402 [ 'apierror-missingrev-title', wfEscapeWikiText( $title->getPrefixedText() ) ], 'nosuchrevid'
403 );
404 } else {
405 $this->dieWithError(
406 [ 'apierror-missingtitle-byname', wfEscapeWikiText( $title->getPrefixedText() ) ],
407 'missingtitle'
408 );
409 }
410 }
411 }
412 }
413 if ( $revId !== null ) {
414 $rev = $this->getRevisionById( $revId );
415 if ( !$rev ) {
416 $this->dieWithError( [ 'apierror-nosuchrevid', $revId ] );
417 }
418 $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
419
420 // If we don't have supplied content, return here. Otherwise,
421 // continue on below with the supplied content.
422 if ( !$suppliedContent ) {
423 $newRev = $rev;
424
425 // Deprecated 'fromsection'/'tosection'
426 if ( isset( $params["{$prefix}section"] ) ) {
427 $section = $params["{$prefix}section"];
428 $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
429 $content = $rev->getContent( SlotRecord::MAIN, RevisionRecord::FOR_THIS_USER,
430 $this->getUser() );
431 if ( !$content ) {
432 $this->dieWithError(
433 [ 'apierror-missingcontent-revid-role', $rev->getId(), SlotRecord::MAIN ], 'missingcontent'
434 );
435 }
436 $content = $content ? $content->getSection( $section ) : null;
437 if ( !$content ) {
438 $this->dieWithError(
439 [ "apierror-compare-nosuch{$prefix}section", wfEscapeWikiText( $section ) ],
440 "nosuch{$prefix}section"
441 );
442 }
443 $newRev->setContent( SlotRecord::MAIN, $content );
444 }
445
446 return [ $newRev, $rev, $rev ];
447 }
448 }
449
450 // Override $content based on supplied text
451 if ( !$title ) {
452 $title = $this->guessTitle();
453 }
454 if ( $rev ) {
455 $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
456 } else {
457 $newRev = $this->revisionStore->newMutableRevisionFromArray( [
458 'title' => $title ?: Title::makeTitle( NS_SPECIAL, 'Badtitle/' . __METHOD__ )
459 ] );
460 }
461 foreach ( $params["{$prefix}slots"] as $role ) {
462 $text = $params["{$prefix}text-{$role}"];
463 if ( $text === null ) {
464 // The SlotRecord::MAIN role can't be deleted
465 if ( $role === SlotRecord::MAIN ) {
466 $this->dieWithError( [ 'apierror-compare-maintextrequired', $prefix ] );
467 }
468
469 // These parameters make no sense without text. Reject them to avoid
470 // confusion.
471 foreach ( [ 'section', 'contentmodel', 'contentformat' ] as $param ) {
472 if ( isset( $params["{$prefix}{$param}-{$role}"] ) ) {
473 $this->dieWithError( [
474 'apierror-compare-notext',
475 wfEscapeWikiText( "{$prefix}{$param}-{$role}" ),
476 wfEscapeWikiText( "{$prefix}text-{$role}" ),
477 ] );
478 }
479 }
480
481 $newRev->removeSlot( $role );
482 continue;
483 }
484
485 $model = $params["{$prefix}contentmodel-{$role}"];
486 $format = $params["{$prefix}contentformat-{$role}"];
487
488 if ( !$model && $rev && $rev->hasSlot( $role ) ) {
489 $model = $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
490 }
491 if ( !$model && $title && $role === SlotRecord::MAIN ) {
492 // @todo: Use SlotRoleRegistry and do this for all slots
493 $model = $title->getContentModel();
494 }
495 if ( !$model ) {
496 $model = $this->guessModel( $role );
497 }
498 if ( !$model ) {
499 $model = CONTENT_MODEL_WIKITEXT;
500 $this->addWarning( [ 'apiwarn-compare-nocontentmodel', $model ] );
501 }
502
503 try {
504 $content = ContentHandler::makeContent( $text, $title, $model, $format );
505 } catch ( MWContentSerializationException $ex ) {
506 $this->dieWithException( $ex, [
507 'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' )
508 ] );
509 }
510
511 if ( $params["{$prefix}pst"] ) {
512 if ( !$title ) {
513 $this->dieWithError( 'apierror-compare-no-title' );
514 }
515 $popts = ParserOptions::newFromContext( $this->getContext() );
516 $content = $content->preSaveTransform( $title, $this->getUser(), $popts );
517 }
518
519 $section = $params["{$prefix}section-{$role}"];
520 if ( $section !== null && $section !== '' ) {
521 if ( !$rev ) {
522 $this->dieWithError( "apierror-compare-no{$prefix}revision" );
523 }
524 $oldContent = $rev->getContent( $role, RevisionRecord::FOR_THIS_USER, $this->getUser() );
525 if ( !$oldContent ) {
526 $this->dieWithError(
527 [ 'apierror-missingcontent-revid-role', $rev->getId(), wfEscapeWikiText( $role ) ],
528 'missingcontent'
529 );
530 }
531 if ( !$oldContent->getContentHandler()->supportsSections() ) {
532 $this->dieWithError( [ 'apierror-sectionsnotsupported', $content->getModel() ] );
533 }
534 try {
535 $content = $oldContent->replaceSection( $section, $content, '' );
536 } catch ( Exception $ex ) {
537 // Probably a content model mismatch.
538 $content = null;
539 }
540 if ( !$content ) {
541 $this->dieWithError( [ 'apierror-sectionreplacefailed' ] );
542 }
543 }
544
545 // Deprecated 'fromsection'/'tosection'
546 if ( $role === SlotRecord::MAIN && isset( $params["{$prefix}section"] ) ) {
547 $section = $params["{$prefix}section"];
548 $content = $content->getSection( $section );
549 if ( !$content ) {
550 $this->dieWithError(
551 [ "apierror-compare-nosuch{$prefix}section", wfEscapeWikiText( $section ) ],
552 "nosuch{$prefix}section"
553 );
554 }
555 }
556
557 $newRev->setContent( $role, $content );
558 }
559 return [ $newRev, $rev, null ];
560 }
561
562 /**
563 * Set value fields from a RevisionRecord object
564 *
565 * @param array &$vals Result array to set data into
566 * @param string $prefix 'from' or 'to'
567 * @param RevisionRecord|null $rev
568 */
569 private function setVals( &$vals, $prefix, $rev ) {
570 if ( $rev ) {
571 $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
572 if ( isset( $this->props['ids'] ) ) {
573 $vals["{$prefix}id"] = $title->getArticleID();
574 $vals["{$prefix}revid"] = $rev->getId();
575 }
576 if ( isset( $this->props['title'] ) ) {
577 ApiQueryBase::addTitleInfo( $vals, $title, $prefix );
578 }
579 if ( isset( $this->props['size'] ) ) {
580 $vals["{$prefix}size"] = $rev->getSize();
581 }
582
583 $anyHidden = false;
584 if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
585 $vals["{$prefix}texthidden"] = true;
586 $anyHidden = true;
587 }
588
589 if ( $rev->isDeleted( RevisionRecord::DELETED_USER ) ) {
590 $vals["{$prefix}userhidden"] = true;
591 $anyHidden = true;
592 }
593 if ( isset( $this->props['user'] ) ) {
594 $user = $rev->getUser( RevisionRecord::FOR_THIS_USER, $this->getUser() );
595 if ( $user ) {
596 $vals["{$prefix}user"] = $user->getName();
597 $vals["{$prefix}userid"] = $user->getId();
598 }
599 }
600
601 if ( $rev->isDeleted( RevisionRecord::DELETED_COMMENT ) ) {
602 $vals["{$prefix}commenthidden"] = true;
603 $anyHidden = true;
604 }
605 if ( isset( $this->props['comment'] ) || isset( $this->props['parsedcomment'] ) ) {
606 $comment = $rev->getComment( RevisionRecord::FOR_THIS_USER, $this->getUser() );
607 if ( $comment !== null ) {
608 if ( isset( $this->props['comment'] ) ) {
609 $vals["{$prefix}comment"] = $comment->text;
610 }
611 $vals["{$prefix}parsedcomment"] = Linker::formatComment(
612 $comment->text, $title
613 );
614 }
615 }
616
617 if ( $anyHidden ) {
618 $this->getMain()->setCacheMode( 'private' );
619 if ( $rev->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
620 $vals["{$prefix}suppressed"] = true;
621 }
622 }
623
624 // @phan-suppress-next-line PhanUndeclaredProperty
625 if ( !empty( $rev->isArchive ) ) {
626 $this->getMain()->setCacheMode( 'private' );
627 $vals["{$prefix}archive"] = true;
628 }
629 }
630 }
631
632 public function getAllowedParams() {
633 $slotRoles = $this->slotRoleRegistry->getKnownRoles();
634 sort( $slotRoles, SORT_STRING );
635
636 // Parameters for the 'from' and 'to' content
637 $fromToParams = [
638 'title' => null,
639 'id' => [
640 ApiBase::PARAM_TYPE => 'integer'
641 ],
642 'rev' => [
643 ApiBase::PARAM_TYPE => 'integer'
644 ],
645
646 'slots' => [
647 ApiBase::PARAM_TYPE => $slotRoles,
648 ApiBase::PARAM_ISMULTI => true,
649 ],
650 'text-{slot}' => [
651 ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
652 ApiBase::PARAM_TYPE => 'text',
653 ],
654 'section-{slot}' => [
655 ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
656 ApiBase::PARAM_TYPE => 'string',
657 ],
658 'contentformat-{slot}' => [
659 ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
660 ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
661 ],
662 'contentmodel-{slot}' => [
663 ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
664 ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
665 ],
666 'pst' => false,
667
668 'text' => [
669 ApiBase::PARAM_TYPE => 'text',
670 ApiBase::PARAM_DEPRECATED => true,
671 ],
672 'contentformat' => [
673 ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
674 ApiBase::PARAM_DEPRECATED => true,
675 ],
676 'contentmodel' => [
677 ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
678 ApiBase::PARAM_DEPRECATED => true,
679 ],
680 'section' => [
681 ApiBase::PARAM_DFLT => null,
682 ApiBase::PARAM_DEPRECATED => true,
683 ],
684 ];
685
686 $ret = [];
687 foreach ( $fromToParams as $k => $v ) {
688 if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) {
689 $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'fromslots';
690 }
691 $ret["from$k"] = $v;
692 }
693 foreach ( $fromToParams as $k => $v ) {
694 if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) {
695 $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'toslots';
696 }
697 $ret["to$k"] = $v;
698 }
699
700 $ret = wfArrayInsertAfter(
701 $ret,
702 [ 'torelative' => [ ApiBase::PARAM_TYPE => [ 'prev', 'next', 'cur' ], ] ],
703 'torev'
704 );
705
706 $ret['prop'] = [
707 ApiBase::PARAM_DFLT => 'diff|ids|title',
708 ApiBase::PARAM_TYPE => [
709 'diff',
710 'diffsize',
711 'rel',
712 'ids',
713 'title',
714 'user',
715 'comment',
716 'parsedcomment',
717 'size',
718 ],
719 ApiBase::PARAM_ISMULTI => true,
720 ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
721 ];
722
723 $ret['slots'] = [
724 ApiBase::PARAM_TYPE => $slotRoles,
725 ApiBase::PARAM_ISMULTI => true,
726 ApiBase::PARAM_ALL => true,
727 ];
728
729 return $ret;
730 }
731
732 protected function getExamplesMessages() {
733 return [
734 'action=compare&fromrev=1&torev=2'
735 => 'apihelp-compare-example-1',
736 ];
737 }
738 }