Completed
Branch master (939199)
by
unknown
39:35
created

includes/api/ApiEditPage.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 *
4
 *
5
 * Created on August 16, 2007
6
 *
7
 * Copyright © 2007 Iker Labarga "<Firstname><Lastname>@gmail.com"
8
 *
9
 * This program is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 2 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License along
20
 * with this program; if not, write to the Free Software Foundation, Inc.,
21
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22
 * http://www.gnu.org/copyleft/gpl.html
23
 *
24
 * @file
25
 */
26
27
/**
28
 * A module that allows for editing and creating pages.
29
 *
30
 * Currently, this wraps around the EditPage class in an ugly way,
31
 * EditPage.php should be rewritten to provide a cleaner interface,
32
 * see T20654 if you're inspired to fix this.
33
 *
34
 * @ingroup API
35
 */
36
class ApiEditPage extends ApiBase {
37
	public function execute() {
38
		$this->useTransactionalTimeLimit();
39
40
		$user = $this->getUser();
41
		$params = $this->extractRequestParams();
42
43
		if ( is_null( $params['text'] ) && is_null( $params['appendtext'] ) &&
44
			is_null( $params['prependtext'] ) &&
45
			$params['undo'] == 0
46
		) {
47
			$this->dieUsageMsg( 'missingtext' );
48
		}
49
50
		$pageObj = $this->getTitleOrPageId( $params );
51
		$titleObj = $pageObj->getTitle();
52
		$apiResult = $this->getResult();
53
54
		if ( $params['redirect'] ) {
55
			if ( $params['prependtext'] === null && $params['appendtext'] === null
56
				&& $params['section'] !== 'new'
57
			) {
58
				$this->dieUsage( 'You have attempted to edit using the "redirect"-following'
59
					. ' mode, which must be used in conjuction with section=new, prependtext'
60
					. ', or appendtext.', 'redirect-appendonly' );
61
			}
62
			if ( $titleObj->isRedirect() ) {
63
				$oldTitle = $titleObj;
64
65
				$titles = Revision::newFromTitle( $oldTitle, false, Revision::READ_LATEST )
66
					->getContent( Revision::FOR_THIS_USER, $user )
67
					->getRedirectChain();
68
				// array_shift( $titles );
69
70
				$redirValues = [];
71
72
				/** @var $newTitle Title */
73
				foreach ( $titles as $id => $newTitle ) {
74
75
					if ( !isset( $titles[$id - 1] ) ) {
76
						$titles[$id - 1] = $oldTitle;
77
					}
78
79
					$redirValues[] = [
80
						'from' => $titles[$id - 1]->getPrefixedText(),
81
						'to' => $newTitle->getPrefixedText()
82
					];
83
84
					$titleObj = $newTitle;
85
				}
86
87
				ApiResult::setIndexedTagName( $redirValues, 'r' );
88
				$apiResult->addValue( null, 'redirects', $redirValues );
89
90
				// Since the page changed, update $pageObj
91
				$pageObj = WikiPage::factory( $titleObj );
92
			}
93
		}
94
95
		if ( !isset( $params['contentmodel'] ) || $params['contentmodel'] == '' ) {
96
			$contentHandler = $pageObj->getContentHandler();
97
		} else {
98
			$contentHandler = ContentHandler::getForModelID( $params['contentmodel'] );
99
		}
100
		$contentModel = $contentHandler->getModelID();
101
102
		$name = $titleObj->getPrefixedDBkey();
103
		$model = $contentHandler->getModelID();
104
105
		if ( $params['undo'] > 0 ) {
106
			// allow undo via api
107
		} elseif ( $contentHandler->supportsDirectApiEditing() === false ) {
108
			$this->dieUsage(
109
				"Direct editing via API is not supported for content model $model used by $name",
110
				'no-direct-editing'
111
			);
112
		}
113
114
		if ( !isset( $params['contentformat'] ) || $params['contentformat'] == '' ) {
115
			$contentFormat = $contentHandler->getDefaultFormat();
116
		} else {
117
			$contentFormat = $params['contentformat'];
118
		}
119
120
		if ( !$contentHandler->isSupportedFormat( $contentFormat ) ) {
121
122
			$this->dieUsage( "The requested format $contentFormat is not supported for content model " .
123
				" $model used by $name", 'badformat' );
124
		}
125
126
		if ( $params['createonly'] && $titleObj->exists() ) {
127
			$this->dieUsageMsg( 'createonly-exists' );
128
		}
129
		if ( $params['nocreate'] && !$titleObj->exists() ) {
130
			$this->dieUsageMsg( 'nocreate-missing' );
131
		}
132
133
		// Now let's check whether we're even allowed to do this
134
		$errors = $titleObj->getUserPermissionsErrors( 'edit', $user );
135
		if ( !$titleObj->exists() ) {
136
			$errors = array_merge( $errors, $titleObj->getUserPermissionsErrors( 'create', $user ) );
137
		}
138
		if ( count( $errors ) ) {
139
			if ( is_array( $errors[0] ) ) {
140
				switch ( $errors[0][0] ) {
141
					case 'blockedtext':
142
						$this->dieUsage(
143
							'You have been blocked from editing',
144
							'blocked',
145
							0,
146
							[ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
147
						);
148
						break;
149
					case 'autoblockedtext':
150
						$this->dieUsage(
151
							'Your IP address has been blocked automatically, because it was used by a blocked user',
152
							'autoblocked',
153
							0,
154
							[ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
0 ignored issues
show
It seems like $user->getBlock() can be null; however, getBlockInfo() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
155
						);
156
						break;
157
					default:
158
						$this->dieUsageMsg( $errors[0] );
159
				}
160
			} else {
161
				$this->dieUsageMsg( $errors[0] );
162
			}
163
		}
164
165
		$toMD5 = $params['text'];
166
		if ( !is_null( $params['appendtext'] ) || !is_null( $params['prependtext'] ) ) {
167
			$content = $pageObj->getContent();
168
169
			if ( !$content ) {
170
				if ( $titleObj->getNamespace() == NS_MEDIAWIKI ) {
171
					# If this is a MediaWiki:x message, then load the messages
172
					# and return the message value for x.
173
					$text = $titleObj->getDefaultMessageText();
174
					if ( $text === false ) {
175
						$text = '';
176
					}
177
178
					try {
179
						$content = ContentHandler::makeContent( $text, $this->getTitle() );
180
					} catch ( MWContentSerializationException $ex ) {
181
						$this->dieUsage( $ex->getMessage(), 'parseerror' );
182
183
						return;
184
					}
185
				} else {
186
					# Otherwise, make a new empty content.
187
					$content = $contentHandler->makeEmptyContent();
188
				}
189
			}
190
191
			// @todo Add support for appending/prepending to the Content interface
192
193
			if ( !( $content instanceof TextContent ) ) {
194
				$mode = $contentHandler->getModelID();
195
				$this->dieUsage( "Can't append to pages using content model $mode", 'appendnotsupported' );
196
			}
197
198
			if ( !is_null( $params['section'] ) ) {
199
				if ( !$contentHandler->supportsSections() ) {
200
					$modelName = $contentHandler->getModelID();
201
					$this->dieUsage(
202
						"Sections are not supported for this content model: $modelName.",
203
						'sectionsnotsupported'
204
					);
205
				}
206
207
				if ( $params['section'] == 'new' ) {
208
					// DWIM if they're trying to prepend/append to a new section.
209
					$content = null;
210
				} else {
211
					// Process the content for section edits
212
					$section = $params['section'];
213
					$content = $content->getSection( $section );
214
215
					if ( !$content ) {
216
						$this->dieUsage( "There is no section {$section}.", 'nosuchsection' );
217
					}
218
				}
219
			}
220
221
			if ( !$content ) {
222
				$text = '';
223
			} else {
224
				$text = $content->serialize( $contentFormat );
225
			}
226
227
			$params['text'] = $params['prependtext'] . $text . $params['appendtext'];
228
			$toMD5 = $params['prependtext'] . $params['appendtext'];
229
		}
230
231
		if ( $params['undo'] > 0 ) {
232
			if ( $params['undoafter'] > 0 ) {
233
				if ( $params['undo'] < $params['undoafter'] ) {
234
					list( $params['undo'], $params['undoafter'] ) =
235
						[ $params['undoafter'], $params['undo'] ];
236
				}
237
				$undoafterRev = Revision::newFromId( $params['undoafter'] );
238
			}
239
			$undoRev = Revision::newFromId( $params['undo'] );
240 View Code Duplication
			if ( is_null( $undoRev ) || $undoRev->isDeleted( Revision::DELETED_TEXT ) ) {
241
				$this->dieUsageMsg( [ 'nosuchrevid', $params['undo'] ] );
242
			}
243
244
			if ( $params['undoafter'] == 0 ) {
245
				$undoafterRev = $undoRev->getPrevious();
246
			}
247 View Code Duplication
			if ( is_null( $undoafterRev ) || $undoafterRev->isDeleted( Revision::DELETED_TEXT ) ) {
248
				$this->dieUsageMsg( [ 'nosuchrevid', $params['undoafter'] ] );
249
			}
250
251 View Code Duplication
			if ( $undoRev->getPage() != $pageObj->getId() ) {
252
				$this->dieUsageMsg( [ 'revwrongpage', $undoRev->getId(),
253
					$titleObj->getPrefixedText() ] );
254
			}
255 View Code Duplication
			if ( $undoafterRev->getPage() != $pageObj->getId() ) {
256
				$this->dieUsageMsg( [ 'revwrongpage', $undoafterRev->getId(),
257
					$titleObj->getPrefixedText() ] );
258
			}
259
260
			$newContent = $contentHandler->getUndoContent(
261
				$pageObj->getRevision(),
262
				$undoRev,
263
				$undoafterRev
264
			);
265
266
			if ( !$newContent ) {
267
				$this->dieUsageMsg( 'undo-failure' );
268
			}
269
			if ( empty( $params['contentmodel'] )
270
				&& empty( $params['contentformat'] )
271
			) {
272
				// If we are reverting content model, the new content model
273
				// might not support the current serialization format, in
274
				// which case go back to the old serialization format,
275
				// but only if the user hasn't specified a format/model
276
				// parameter.
277
				if ( !$newContent->isSupportedFormat( $contentFormat ) ) {
278
					$contentFormat = $undoafterRev->getContentFormat();
279
				}
280
				// Override content model with model of undid revision.
281
				$contentModel = $newContent->getModel();
282
			}
283
			$params['text'] = $newContent->serialize( $contentFormat );
284
			// If no summary was given and we only undid one rev,
285
			// use an autosummary
286
			if ( is_null( $params['summary'] ) &&
287
				$titleObj->getNextRevisionID( $undoafterRev->getId() ) == $params['undo']
288
			) {
289
				$params['summary'] = wfMessage( 'undo-summary' )
290
					->params( $params['undo'], $undoRev->getUserText() )->inContentLanguage()->text();
291
			}
292
		}
293
294
		// See if the MD5 hash checks out
295
		if ( !is_null( $params['md5'] ) && md5( $toMD5 ) !== $params['md5'] ) {
296
			$this->dieUsageMsg( 'hashcheckfailed' );
297
		}
298
299
		// EditPage wants to parse its stuff from a WebRequest
300
		// That interface kind of sucks, but it's workable
301
		$requestArray = [
302
			'wpTextbox1' => $params['text'],
303
			'format' => $contentFormat,
304
			'model' => $contentModel,
305
			'wpEditToken' => $params['token'],
306
			'wpIgnoreBlankSummary' => true,
307
			'wpIgnoreBlankArticle' => true,
308
			'wpIgnoreSelfRedirect' => true,
309
			'bot' => $params['bot'],
310
		];
311
312
		if ( !is_null( $params['summary'] ) ) {
313
			$requestArray['wpSummary'] = $params['summary'];
314
		}
315
316
		if ( !is_null( $params['sectiontitle'] ) ) {
317
			$requestArray['wpSectionTitle'] = $params['sectiontitle'];
318
		}
319
320
		// TODO: Pass along information from 'undoafter' as well
321
		if ( $params['undo'] > 0 ) {
322
			$requestArray['wpUndidRevision'] = $params['undo'];
323
		}
324
325
		// Watch out for basetimestamp == '' or '0'
326
		// It gets treated as NOW, almost certainly causing an edit conflict
327
		if ( $params['basetimestamp'] !== null && (bool)$this->getMain()->getVal( 'basetimestamp' ) ) {
328
			$requestArray['wpEdittime'] = $params['basetimestamp'];
329
		} else {
330
			$requestArray['wpEdittime'] = $pageObj->getTimestamp();
331
		}
332
333
		if ( $params['starttimestamp'] !== null ) {
334
			$requestArray['wpStarttime'] = $params['starttimestamp'];
335
		} else {
336
			$requestArray['wpStarttime'] = wfTimestampNow(); // Fake wpStartime
337
		}
338
339
		if ( $params['minor'] || ( !$params['notminor'] && $user->getOption( 'minordefault' ) ) ) {
340
			$requestArray['wpMinoredit'] = '';
341
		}
342
343
		if ( $params['recreate'] ) {
344
			$requestArray['wpRecreate'] = '';
345
		}
346
347
		if ( !is_null( $params['section'] ) ) {
348
			$section = $params['section'];
349
			if ( !preg_match( '/^((T-)?\d+|new)$/', $section ) ) {
350
				$this->dieUsage( "The section parameter must be a valid section id or 'new'",
351
					'invalidsection' );
352
			}
353
			$content = $pageObj->getContent();
354
			if ( $section !== '0' && $section != 'new'
355
				&& ( !$content || !$content->getSection( $section ) )
356
			) {
357
				$this->dieUsage( "There is no section {$section}.", 'nosuchsection' );
358
			}
359
			$requestArray['wpSection'] = $params['section'];
360
		} else {
361
			$requestArray['wpSection'] = '';
362
		}
363
364
		$watch = $this->getWatchlistValue( $params['watchlist'], $titleObj );
365
366
		// Deprecated parameters
367
		if ( $params['watch'] ) {
368
			$watch = true;
369
		} elseif ( $params['unwatch'] ) {
370
			$watch = false;
371
		}
372
373
		if ( $watch ) {
374
			$requestArray['wpWatchthis'] = '';
375
		}
376
377
		// Apply change tags
378
		if ( count( $params['tags'] ) ) {
379
			$tagStatus = ChangeTags::canAddTagsAccompanyingChange( $params['tags'], $user );
380
			if ( $tagStatus->isOK() ) {
381
				$requestArray['wpChangeTags'] = implode( ',', $params['tags'] );
382
			} else {
383
				$this->dieStatus( $tagStatus );
384
			}
385
		}
386
387
		// Pass through anything else we might have been given, to support extensions
388
		// This is kind of a hack but it's the best we can do to make extensions work
389
		$requestArray += $this->getRequest()->getValues();
390
391
		global $wgTitle, $wgRequest;
392
393
		$req = new DerivativeRequest( $this->getRequest(), $requestArray, true );
394
395
		// Some functions depend on $wgTitle == $ep->mTitle
396
		// TODO: Make them not or check if they still do
397
		$wgTitle = $titleObj;
398
399
		$articleContext = new RequestContext;
400
		$articleContext->setRequest( $req );
401
		$articleContext->setWikiPage( $pageObj );
402
		$articleContext->setUser( $this->getUser() );
403
404
		/** @var $articleObject Article */
405
		$articleObject = Article::newFromWikiPage( $pageObj, $articleContext );
406
407
		$ep = new EditPage( $articleObject );
408
409
		$ep->setApiEditOverride( true );
410
		$ep->setContextTitle( $titleObj );
411
		$ep->importFormData( $req );
412
		$content = $ep->textbox1;
413
414
		// Run hooks
415
		// Handle APIEditBeforeSave parameters
416
		$r = [];
417
		// Deprecated in favour of EditFilterMergedContent
418
		if ( !Hooks::run( 'APIEditBeforeSave', [ $ep, $content, &$r ], '1.28' ) ) {
419
			if ( count( $r ) ) {
420
				$r['result'] = 'Failure';
421
				$apiResult->addValue( null, $this->getModuleName(), $r );
422
423
				return;
424
			}
425
426
			$this->dieUsageMsg( 'hookaborted' );
427
		}
428
429
		// Do the actual save
430
		$oldRevId = $articleObject->getRevIdFetched();
431
		$result = null;
432
		// Fake $wgRequest for some hooks inside EditPage
433
		// @todo FIXME: This interface SUCKS
434
		$oldRequest = $wgRequest;
435
		$wgRequest = $req;
436
437
		$status = $ep->attemptSave( $result );
438
		$wgRequest = $oldRequest;
439
440
		switch ( $status->value ) {
441
			case EditPage::AS_HOOK_ERROR:
442
			case EditPage::AS_HOOK_ERROR_EXPECTED:
443
				if ( isset( $status->apiHookResult ) ) {
444
					$r = $status->apiHookResult;
445
					$r['result'] = 'Failure';
446
					$apiResult->addValue( null, $this->getModuleName(), $r );
447
					return;
448
				} else {
449
					$this->dieUsageMsg( 'hookaborted' );
450
				}
451
452
			case EditPage::AS_PARSE_ERROR:
453
				$this->dieUsage( $status->getMessage(), 'parseerror' );
454
455
			case EditPage::AS_IMAGE_REDIRECT_ANON:
456
				$this->dieUsageMsg( 'noimageredirect-anon' );
457
458
			case EditPage::AS_IMAGE_REDIRECT_LOGGED:
459
				$this->dieUsageMsg( 'noimageredirect-logged' );
460
461
			case EditPage::AS_SPAM_ERROR:
462
				$this->dieUsageMsg( [ 'spamdetected', $result['spam'] ] );
463
464
			case EditPage::AS_BLOCKED_PAGE_FOR_USER:
465
				$this->dieUsage(
466
					'You have been blocked from editing',
467
					'blocked',
468
					0,
469
					[ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
470
				);
471
472
			case EditPage::AS_MAX_ARTICLE_SIZE_EXCEEDED:
473
			case EditPage::AS_CONTENT_TOO_BIG:
474
				$this->dieUsageMsg( [ 'contenttoobig', $this->getConfig()->get( 'MaxArticleSize' ) ] );
475
476
			case EditPage::AS_READ_ONLY_PAGE_ANON:
477
				$this->dieUsageMsg( 'noedit-anon' );
478
479
			case EditPage::AS_READ_ONLY_PAGE_LOGGED:
480
				$this->dieUsageMsg( 'noedit' );
481
482
			case EditPage::AS_READ_ONLY_PAGE:
483
				$this->dieReadOnly();
484
485
			case EditPage::AS_RATE_LIMITED:
486
				$this->dieUsageMsg( 'actionthrottledtext' );
487
488
			case EditPage::AS_ARTICLE_WAS_DELETED:
489
				$this->dieUsageMsg( 'wasdeleted' );
490
491
			case EditPage::AS_NO_CREATE_PERMISSION:
492
				$this->dieUsageMsg( 'nocreate-loggedin' );
493
494
			case EditPage::AS_NO_CHANGE_CONTENT_MODEL:
495
				$this->dieUsageMsg( 'cantchangecontentmodel' );
496
497
			case EditPage::AS_BLANK_ARTICLE:
498
				$this->dieUsageMsg( 'blankpage' );
499
500
			case EditPage::AS_CONFLICT_DETECTED:
501
				$this->dieUsageMsg( 'editconflict' );
502
503
			case EditPage::AS_TEXTBOX_EMPTY:
504
				$this->dieUsageMsg( 'emptynewsection' );
505
506
			case EditPage::AS_CHANGE_TAG_ERROR:
507
				$this->dieStatus( $status );
508
509
			case EditPage::AS_SUCCESS_NEW_ARTICLE:
510
				$r['new'] = true;
511
				// fall-through
512
513
			case EditPage::AS_SUCCESS_UPDATE:
514
				$r['result'] = 'Success';
515
				$r['pageid'] = intval( $titleObj->getArticleID() );
516
				$r['title'] = $titleObj->getPrefixedText();
517
				$r['contentmodel'] = $articleObject->getContentModel();
518
				$newRevId = $articleObject->getLatest();
519
				if ( $newRevId == $oldRevId ) {
520
					$r['nochange'] = true;
521
				} else {
522
					$r['oldrevid'] = intval( $oldRevId );
523
					$r['newrevid'] = intval( $newRevId );
524
					$r['newtimestamp'] = wfTimestamp( TS_ISO_8601,
525
						$pageObj->getTimestamp() );
526
				}
527
				break;
528
529
			case EditPage::AS_SUMMARY_NEEDED:
530
				// Shouldn't happen since we set wpIgnoreBlankSummary, but just in case
531
				$this->dieUsageMsg( 'summaryrequired' );
532
533
			case EditPage::AS_END:
534
			default:
535
				// $status came from WikiPage::doEditContent()
536
				$errors = $status->getErrorsArray();
537
				$this->dieUsageMsg( $errors[0] ); // TODO: Add new errors to message map
538
				break;
539
		}
540
		$apiResult->addValue( null, $this->getModuleName(), $r );
541
	}
542
543
	public function mustBePosted() {
544
		return true;
545
	}
546
547
	public function isWriteMode() {
548
		return true;
549
	}
550
551
	public function getAllowedParams() {
552
		return [
553
			'title' => [
554
				ApiBase::PARAM_TYPE => 'string',
555
			],
556
			'pageid' => [
557
				ApiBase::PARAM_TYPE => 'integer',
558
			],
559
			'section' => null,
560
			'sectiontitle' => [
561
				ApiBase::PARAM_TYPE => 'string',
562
			],
563
			'text' => [
564
				ApiBase::PARAM_TYPE => 'text',
565
			],
566
			'summary' => null,
567
			'tags' => [
568
				ApiBase::PARAM_TYPE => 'tags',
569
				ApiBase::PARAM_ISMULTI => true,
570
			],
571
			'minor' => false,
572
			'notminor' => false,
573
			'bot' => false,
574
			'basetimestamp' => [
575
				ApiBase::PARAM_TYPE => 'timestamp',
576
			],
577
			'starttimestamp' => [
578
				ApiBase::PARAM_TYPE => 'timestamp',
579
			],
580
			'recreate' => false,
581
			'createonly' => false,
582
			'nocreate' => false,
583
			'watch' => [
584
				ApiBase::PARAM_DFLT => false,
585
				ApiBase::PARAM_DEPRECATED => true,
586
			],
587
			'unwatch' => [
588
				ApiBase::PARAM_DFLT => false,
589
				ApiBase::PARAM_DEPRECATED => true,
590
			],
591
			'watchlist' => [
592
				ApiBase::PARAM_DFLT => 'preferences',
593
				ApiBase::PARAM_TYPE => [
594
					'watch',
595
					'unwatch',
596
					'preferences',
597
					'nochange'
598
				],
599
			],
600
			'md5' => null,
601
			'prependtext' => [
602
				ApiBase::PARAM_TYPE => 'text',
603
			],
604
			'appendtext' => [
605
				ApiBase::PARAM_TYPE => 'text',
606
			],
607
			'undo' => [
608
				ApiBase::PARAM_TYPE => 'integer'
609
			],
610
			'undoafter' => [
611
				ApiBase::PARAM_TYPE => 'integer'
612
			],
613
			'redirect' => [
614
				ApiBase::PARAM_TYPE => 'boolean',
615
				ApiBase::PARAM_DFLT => false,
616
			],
617
			'contentformat' => [
618
				ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
619
			],
620
			'contentmodel' => [
621
				ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
622
			],
623
			'token' => [
624
				// Standard definition automatically inserted
625
				ApiBase::PARAM_HELP_MSG_APPEND => [ 'apihelp-edit-param-token' ],
626
			],
627
		];
628
	}
629
630
	public function needsToken() {
631
		return 'csrf';
632
	}
633
634
	protected function getExamplesMessages() {
635
		return [
636
			'action=edit&title=Test&summary=test%20summary&' .
637
				'text=article%20content&basetimestamp=2007-08-24T12:34:54Z&token=123ABC'
638
				=> 'apihelp-edit-example-edit',
639
			'action=edit&title=Test&summary=NOTOC&minor=&' .
640
				'prependtext=__NOTOC__%0A&basetimestamp=2007-08-24T12:34:54Z&token=123ABC'
641
				=> 'apihelp-edit-example-prepend',
642
			'action=edit&title=Test&undo=13585&undoafter=13579&' .
643
				'basetimestamp=2007-08-24T12:34:54Z&token=123ABC'
644
				=> 'apihelp-edit-example-undo',
645
		];
646
	}
647
648
	public function getHelpUrls() {
649
		return 'https://www.mediawiki.org/wiki/API:Edit';
650
	}
651
}
652