MediaWiki REL1_37
ApiEditPage.php
Go to the documentation of this file.
1<?php
31
48class ApiEditPage extends ApiBase {
50
53
56
59
62
65
76 public function __construct(
77 ApiMain $mainModule,
78 $moduleName,
85 ) {
86 parent::__construct( $mainModule, $moduleName );
87
88 // This class is extended and therefor fallback to global state - T264213
89 $services = MediaWikiServices::getInstance();
90 $this->contentHandlerFactory = $contentHandlerFactory ?? $services->getContentHandlerFactory();
91 $this->revisionLookup = $revisionLookup ?? $services->getRevisionLookup();
92 $this->watchedItemStore = $watchedItemStore ?? $services->getWatchedItemStore();
93 $this->wikiPageFactory = $wikiPageFactory ?? $services->getWikiPageFactory();
94
95 // Variables needed in ApiWatchlistTrait trait
96 $this->watchlistExpiryEnabled = $this->getConfig()->get( 'WatchlistExpiry' );
97 $this->watchlistMaxDuration = $this->getConfig()->get( 'WatchlistExpiryMaxDuration' );
98 $this->watchlistManager = $watchlistManager ?? $services->getWatchlistManager();
99 $this->userOptionsLookup = $userOptionsLookup ?? $services->getUserOptionsLookup();
100 }
101
102 public function execute() {
104
105 $user = $this->getUser();
106 $params = $this->extractRequestParams();
107
108 $this->requireAtLeastOneParameter( $params, 'text', 'appendtext', 'prependtext', 'undo' );
109
110 $pageObj = $this->getTitleOrPageId( $params );
111 $titleObj = $pageObj->getTitle();
112 $this->getErrorFormatter()->setContextTitle( $titleObj );
113 $apiResult = $this->getResult();
114
115 if ( $params['redirect'] ) {
116 if ( $params['prependtext'] === null
117 && $params['appendtext'] === null
118 && $params['section'] !== 'new'
119 ) {
120 $this->dieWithError( 'apierror-redirect-appendonly' );
121 }
122 if ( $titleObj->isRedirect() ) {
123 $oldTitle = $titleObj;
124
125 $titles = $this->revisionLookup
126 ->getRevisionByTitle( $oldTitle, 0, IDBAccessObject::READ_LATEST )
127 ->getContent( SlotRecord::MAIN, RevisionRecord::FOR_THIS_USER, $user )
128 ->getRedirectChain();
129 // array_shift( $titles );
130 '@phan-var Title[] $titles';
131
132 $redirValues = [];
133
135 foreach ( $titles as $id => $newTitle ) {
136 $titles[$id - 1] = $titles[$id - 1] ?? $oldTitle;
137
138 $redirValues[] = [
139 'from' => $titles[$id - 1]->getPrefixedText(),
140 'to' => $newTitle->getPrefixedText()
141 ];
142
143 $titleObj = $newTitle;
144
145 // T239428: Check whether the new title is valid
146 if ( $titleObj->isExternal() || !$titleObj->canExist() ) {
147 $redirValues[count( $redirValues ) - 1]['to'] = $titleObj->getFullText();
148 $this->dieWithError(
149 [
150 'apierror-edit-invalidredirect',
151 Message::plaintextParam( $oldTitle->getPrefixedText() ),
152 Message::plaintextParam( $titleObj->getFullText() ),
153 ],
154 'edit-invalidredirect',
155 [ 'redirects' => $redirValues ]
156 );
157 }
158 }
159
160 ApiResult::setIndexedTagName( $redirValues, 'r' );
161 $apiResult->addValue( null, 'redirects', $redirValues );
162
163 // Since the page changed, update $pageObj
164 $pageObj = $this->wikiPageFactory->newFromTitle( $titleObj );
165 $this->getErrorFormatter()->setContextTitle( $titleObj );
166 }
167 }
168
169 if ( $params['contentmodel'] ) {
170 $contentHandler = $this->contentHandlerFactory->getContentHandler( $params['contentmodel'] );
171 } else {
172 $contentHandler = $pageObj->getContentHandler();
173 }
174 $contentModel = $contentHandler->getModelID();
175
176 $name = $titleObj->getPrefixedDBkey();
177
178 if ( $params['undo'] > 0 ) {
179 // allow undo via api
180 } elseif ( $contentHandler->supportsDirectApiEditing() === false ) {
181 $this->dieWithError( [ 'apierror-no-direct-editing', $contentModel, $name ] );
182 }
183
184 $contentFormat = $params['contentformat'] ?: $contentHandler->getDefaultFormat();
185
186 if ( !$contentHandler->isSupportedFormat( $contentFormat ) ) {
187 $this->dieWithError( [ 'apierror-badformat', $contentFormat, $contentModel, $name ] );
188 }
189
190 if ( $params['createonly'] && $titleObj->exists() ) {
191 $this->dieWithError( 'apierror-articleexists' );
192 }
193 if ( $params['nocreate'] && !$titleObj->exists() ) {
194 $this->dieWithError( 'apierror-missingtitle' );
195 }
196
197 // Now let's check whether we're even allowed to do this
199 $titleObj,
200 'edit',
201 [ 'autoblock' => true ]
202 );
203
204 $toMD5 = $params['text'];
205 if ( $params['appendtext'] !== null || $params['prependtext'] !== null ) {
206 $content = $pageObj->getContent();
207
208 if ( !$content ) {
209 if ( $titleObj->getNamespace() === NS_MEDIAWIKI ) {
210 # If this is a MediaWiki:x message, then load the messages
211 # and return the message value for x.
212 $text = $titleObj->getDefaultMessageText();
213 if ( $text === false ) {
214 $text = '';
215 }
216
217 try {
218 $content = ContentHandler::makeContent( $text, $titleObj );
219 } catch ( MWContentSerializationException $ex ) {
220 $this->dieWithException( $ex, [
221 'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' )
222 ] );
223 }
224 } else {
225 # Otherwise, make a new empty content.
226 $content = $contentHandler->makeEmptyContent();
227 }
228 }
229
230 // @todo Add support for appending/prepending to the Content interface
231
232 if ( !( $content instanceof TextContent ) ) {
233 $this->dieWithError( [ 'apierror-appendnotsupported', $contentModel ] );
234 }
235
236 if ( $params['section'] !== null ) {
237 if ( !$contentHandler->supportsSections() ) {
238 $this->dieWithError( [ 'apierror-sectionsnotsupported', $contentModel ] );
239 }
240
241 if ( $params['section'] == 'new' ) {
242 // DWIM if they're trying to prepend/append to a new section.
243 $content = null;
244 } else {
245 // Process the content for section edits
246 $section = $params['section'];
247 $content = $content->getSection( $section );
248
249 if ( !$content ) {
250 $this->dieWithError( [ 'apierror-nosuchsection', wfEscapeWikiText( $section ) ] );
251 }
252 }
253 }
254
255 if ( !$content ) {
256 $text = '';
257 } else {
258 $text = $content->serialize( $contentFormat );
259 }
260
261 $params['text'] = $params['prependtext'] . $text . $params['appendtext'];
262 $toMD5 = $params['prependtext'] . $params['appendtext'];
263 }
264
265 if ( $params['undo'] > 0 ) {
266 $undoRev = $this->revisionLookup->getRevisionById( $params['undo'] );
267 if ( $undoRev === null || $undoRev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
268 $this->dieWithError( [ 'apierror-nosuchrevid', $params['undo'] ] );
269 }
270
271 if ( $params['undoafter'] > 0 ) {
272 $undoafterRev = $this->revisionLookup->getRevisionById( $params['undoafter'] );
273 }
274 if ( $params['undoafter'] == 0 ) {
275 $undoafterRev = $this->revisionLookup->getPreviousRevision( $undoRev );
276 }
277 if ( $undoafterRev === null || $undoafterRev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
278 $this->dieWithError( [ 'apierror-nosuchrevid', $params['undoafter'] ] );
279 }
280
281 if ( $undoRev->getPageId() != $pageObj->getId() ) {
282 $this->dieWithError( [ 'apierror-revwrongpage', $undoRev->getId(),
283 $titleObj->getPrefixedText() ] );
284 }
285 if ( $undoafterRev->getPageId() != $pageObj->getId() ) {
286 $this->dieWithError( [ 'apierror-revwrongpage', $undoafterRev->getId(),
287 $titleObj->getPrefixedText() ] );
288 }
289
290 $newContent = $contentHandler->getUndoContent(
291 $pageObj->getRevisionRecord()->getContent( SlotRecord::MAIN ),
292 $undoRev->getContent( SlotRecord::MAIN ),
293 $undoafterRev->getContent( SlotRecord::MAIN ),
294 $pageObj->getRevisionRecord()->getId() === $undoRev->getId()
295 );
296
297 if ( !$newContent ) {
298 $this->dieWithError( 'undo-failure', 'undofailure' );
299 }
300 if ( !$params['contentmodel'] && !$params['contentformat'] ) {
301 // If we are reverting content model, the new content model
302 // might not support the current serialization format, in
303 // which case go back to the old serialization format,
304 // but only if the user hasn't specified a format/model
305 // parameter.
306 if ( !$newContent->isSupportedFormat( $contentFormat ) ) {
307 $undoafterRevMainSlot = $undoafterRev->getSlot(
308 SlotRecord::MAIN,
309 RevisionRecord::RAW
310 );
311 $contentFormat = $undoafterRevMainSlot->getFormat();
312 if ( !$contentFormat ) {
313 // fall back to default content format for the model
314 // of $undoafterRev
315 $contentFormat = $this->contentHandlerFactory
316 ->getContentHandler( $undoafterRevMainSlot->getModel() )
317 ->getDefaultFormat();
318 }
319 }
320 // Override content model with model of undid revision.
321 $contentModel = $newContent->getModel();
322 $undoContentModel = true;
323 }
324 $params['text'] = $newContent->serialize( $contentFormat );
325 // If no summary was given and we only undid one rev,
326 // use an autosummary
327
328 if ( $params['summary'] === null ) {
329 $nextRev = $this->revisionLookup->getNextRevision( $undoafterRev );
330 if ( $nextRev && $nextRev->getId() == $params['undo'] ) {
331 $undoRevUser = $undoRev->getUser();
332 $params['summary'] = wfMessage( 'undo-summary' )
333 ->params( $params['undo'], $undoRevUser ? $undoRevUser->getName() : '' )
334 ->inContentLanguage()->text();
335 }
336 }
337 }
338
339 // See if the MD5 hash checks out
340 if ( $params['md5'] !== null && md5( $toMD5 ) !== $params['md5'] ) {
341 $this->dieWithError( 'apierror-badmd5' );
342 }
343
344 // EditPage wants to parse its stuff from a WebRequest
345 // That interface kind of sucks, but it's workable
346 $requestArray = [
347 'wpTextbox1' => $params['text'],
348 'format' => $contentFormat,
349 'model' => $contentModel,
350 'wpEditToken' => $params['token'],
351 'wpIgnoreBlankSummary' => true,
352 'wpIgnoreBlankArticle' => true,
353 'wpIgnoreSelfRedirect' => true,
354 'bot' => $params['bot'],
355 'wpUnicodeCheck' => EditPage::UNICODE_CHECK,
356 ];
357
358 if ( $params['summary'] !== null ) {
359 $requestArray['wpSummary'] = $params['summary'];
360 }
361
362 if ( $params['sectiontitle'] !== null ) {
363 $requestArray['wpSectionTitle'] = $params['sectiontitle'];
364 }
365
366 if ( $params['undo'] > 0 ) {
367 $requestArray['wpUndidRevision'] = $params['undo'];
368 }
369 if ( $params['undoafter'] > 0 ) {
370 $requestArray['wpUndoAfter'] = $params['undoafter'];
371 }
372
373 // Skip for baserevid == null or '' or '0' or 0
374 if ( !empty( $params['baserevid'] ) ) {
375 $requestArray['editRevId'] = $params['baserevid'];
376 }
377
378 // Watch out for basetimestamp == '' or '0'
379 // It gets treated as NOW, almost certainly causing an edit conflict
380 if ( $params['basetimestamp'] !== null && (bool)$this->getMain()->getVal( 'basetimestamp' ) ) {
381 $requestArray['wpEdittime'] = $params['basetimestamp'];
382 } elseif ( empty( $params['baserevid'] ) ) {
383 // Only set if baserevid is not set. Otherwise, conflicts would be ignored,
384 // due to the way userWasLastToEdit() works.
385 $requestArray['wpEdittime'] = $pageObj->getTimestamp();
386 }
387
388 if ( $params['starttimestamp'] !== null ) {
389 $requestArray['wpStarttime'] = $params['starttimestamp'];
390 } else {
391 $requestArray['wpStarttime'] = wfTimestampNow(); // Fake wpStartime
392 }
393
394 if ( $params['minor'] || ( !$params['notminor'] &&
395 $this->userOptionsLookup->getOption( $user, 'minordefault' ) )
396 ) {
397 $requestArray['wpMinoredit'] = '';
398 }
399
400 if ( $params['recreate'] ) {
401 $requestArray['wpRecreate'] = '';
402 }
403
404 if ( $params['section'] !== null ) {
405 $section = $params['section'];
406 if ( !preg_match( '/^((T-)?\d+|new)$/', $section ) ) {
407 $this->dieWithError( 'apierror-invalidsection' );
408 }
409 $content = $pageObj->getContent();
410 if ( $section !== '0'
411 && $section != 'new'
412 && ( !$content || !$content->getSection( $section ) )
413 ) {
414 $this->dieWithError( [ 'apierror-nosuchsection', $section ] );
415 }
416 $requestArray['wpSection'] = $params['section'];
417 } else {
418 $requestArray['wpSection'] = '';
419 }
420
421 $watch = $this->getWatchlistValue( $params['watchlist'], $titleObj, $user );
422
423 // Deprecated parameters
424 if ( $params['watch'] ) {
425 $watch = true;
426 } elseif ( $params['unwatch'] ) {
427 $watch = false;
428 }
429
430 if ( $watch ) {
431 $requestArray['wpWatchthis'] = true;
432 $watchlistExpiry = $this->getExpiryFromParams( $params );
433
434 if ( $watchlistExpiry ) {
435 $requestArray['wpWatchlistExpiry'] = $watchlistExpiry;
436 }
437 }
438
439 // Apply change tags
440 if ( $params['tags'] ) {
441 $tagStatus = ChangeTags::canAddTagsAccompanyingChange( $params['tags'], $this->getAuthority() );
442 if ( $tagStatus->isOK() ) {
443 $requestArray['wpChangeTags'] = implode( ',', $params['tags'] );
444 } else {
445 $this->dieStatus( $tagStatus );
446 }
447 }
448
449 // Pass through anything else we might have been given, to support extensions
450 // This is kind of a hack but it's the best we can do to make extensions work
451 $requestArray += $this->getRequest()->getValues();
452
453 global $wgTitle, $wgRequest;
454
455 $req = new DerivativeRequest( $this->getRequest(), $requestArray, true );
456
457 // Some functions depend on $wgTitle == $ep->mTitle
458 // TODO: Make them not or check if they still do
459 $wgTitle = $titleObj;
460
461 $articleContext = new RequestContext;
462 $articleContext->setRequest( $req );
463 $articleContext->setWikiPage( $pageObj );
464 $articleContext->setUser( $this->getUser() );
465
467 $articleObject = Article::newFromWikiPage( $pageObj, $articleContext );
468
469 $ep = new EditPage( $articleObject );
470
471 $ep->setApiEditOverride( true );
472 $ep->setContextTitle( $titleObj );
473 $ep->importFormData( $req );
474
475 // T255700: Ensure content models of the base content
476 // and fetched revision remain the same before attempting to save.
477 $editRevId = $requestArray['editRevId'] ?? false;
478 $baseRev = $this->revisionLookup->getRevisionByTitle( $titleObj, $editRevId );
479 $baseContentModel = $baseRev
480 ? $baseRev->getContent( SlotRecord::MAIN )->getModel()
481 : $pageObj->getContentModel();
482
483 // However, allow the content models to possibly differ if we are intentionally
484 // changing them or we are doing an undo edit that is reverting content model change.
485 $contentModelsCanDiffer = $params['contentmodel'] || isset( $undoContentModel );
486
487 if ( !$contentModelsCanDiffer && $contentModel !== $baseContentModel ) {
488 $this->dieWithError( [ 'apierror-contentmodel-mismatch', $contentModel, $baseContentModel ] );
489 }
490
491 // Do the actual save
492 $oldRevId = $articleObject->getRevIdFetched();
493 $result = null;
494
495 // Fake $wgRequest for some hooks inside EditPage
496 // @todo FIXME: This interface SUCKS
497 $oldRequest = $wgRequest;
498 $wgRequest = $req;
499
500 $status = $ep->attemptSave( $result );
501 $statusValue = is_int( $status->value ) ? $status->value : 0;
502 $wgRequest = $oldRequest;
503
504 $r = [];
505 switch ( $statusValue ) {
508 if ( isset( $status->apiHookResult ) ) {
509 $r = $status->apiHookResult;
510 $r['result'] = 'Failure';
511 $apiResult->addValue( null, $this->getModuleName(), $r );
512 return;
513 }
514 if ( !$status->getErrors() ) {
515 // This appears to be unreachable right now, because all
516 // code paths will set an error. Could change, though.
517 $status->fatal( 'hookaborted' ); // @codeCoverageIgnore
518 }
519 $this->dieStatus( $status );
520
521 // These two cases will normally have been caught earlier, and will
522 // only occur if something blocks the user between the earlier
523 // check and the check in EditPage (presumably a hook). It's not
524 // obvious that this is even possible.
525 // @codeCoverageIgnoreStart
527 $this->dieBlocked( $user->getBlock() );
528 // dieBlocked prevents continuation
529
531 $this->dieReadOnly();
532 // @codeCoverageIgnoreEnd
533
535 $r['new'] = true;
536 // fall-through
537
539 $r['result'] = 'Success';
540 $r['pageid'] = (int)$titleObj->getArticleID();
541 $r['title'] = $titleObj->getPrefixedText();
542 $r['contentmodel'] = $articleObject->getPage()->getContentModel();
543 $newRevId = $articleObject->getPage()->getLatest();
544 if ( $newRevId == $oldRevId ) {
545 $r['nochange'] = true;
546 } else {
547 $r['oldrevid'] = (int)$oldRevId;
548 $r['newrevid'] = (int)$newRevId;
549 $r['newtimestamp'] = wfTimestamp( TS_ISO_8601,
550 $pageObj->getTimestamp() );
551 }
552
553 if ( $watch ) {
554 $r['watched'] = true;
555
556 $watchlistExpiry = $this->getWatchlistExpiry(
557 $this->watchedItemStore,
558 $titleObj,
559 $user
560 );
561
562 if ( $watchlistExpiry ) {
563 $r['watchlistexpiry'] = $watchlistExpiry;
564 }
565 }
566 break;
567
568 default:
569 if ( !$status->getErrors() ) {
570 // EditPage sometimes only sets the status code without setting
571 // any actual error messages. Supply defaults for those cases.
572 switch ( $statusValue ) {
573 // Currently needed
575 $status->fatal( 'apierror-noimageredirect-anon' );
576 break;
578 $status->fatal( 'apierror-noimageredirect' );
579 break;
582 $status->fatal( 'apierror-contenttoobig', $this->getConfig()->get( 'MaxArticleSize' ) );
583 break;
585 $status->fatal( 'apierror-noedit-anon' );
586 break;
588 $status->fatal( 'apierror-cantchangecontentmodel' );
589 break;
591 $status->fatal( 'apierror-pagedeleted' );
592 break;
594 $status->fatal( 'edit-conflict' );
595 break;
596
597 // Currently shouldn't be needed, but here in case
598 // hooks use them without setting appropriate
599 // errors on the status.
600 // @codeCoverageIgnoreStart
602 $status->fatal( 'apierror-spamdetected', $result['spam'] );
603 break;
605 $status->fatal( 'apierror-noedit' );
606 break;
608 $status->fatal( 'apierror-ratelimited' );
609 break;
611 $status->fatal( 'nocreate-loggedin' );
612 break;
614 $status->fatal( 'apierror-emptypage' );
615 break;
617 $status->fatal( 'apierror-emptynewsection' );
618 break;
620 $status->fatal( 'apierror-summaryrequired' );
621 break;
622 default:
623 wfWarn( __METHOD__ . ": Unknown EditPage code $statusValue with no message" );
624 $status->fatal( 'apierror-unknownerror-editpage', $statusValue );
625 break;
626 // @codeCoverageIgnoreEnd
627 }
628 }
629 $this->dieStatus( $status );
630 }
631 $apiResult->addValue( null, $this->getModuleName(), $r );
632 }
633
634 public function mustBePosted() {
635 return true;
636 }
637
638 public function isWriteMode() {
639 return true;
640 }
641
642 public function getAllowedParams() {
643 $params = [
644 'title' => [
645 ApiBase::PARAM_TYPE => 'string',
646 ],
647 'pageid' => [
648 ApiBase::PARAM_TYPE => 'integer',
649 ],
650 'section' => null,
651 'sectiontitle' => [
652 ApiBase::PARAM_TYPE => 'string',
653 ],
654 'text' => [
655 ApiBase::PARAM_TYPE => 'text',
656 ],
657 'summary' => null,
658 'tags' => [
659 ApiBase::PARAM_TYPE => 'tags',
661 ],
662 'minor' => false,
663 'notminor' => false,
664 'bot' => false,
665 'baserevid' => [
666 ApiBase::PARAM_TYPE => 'integer',
667 ],
668 'basetimestamp' => [
669 ApiBase::PARAM_TYPE => 'timestamp',
670 ],
671 'starttimestamp' => [
672 ApiBase::PARAM_TYPE => 'timestamp',
673 ],
674 'recreate' => false,
675 'createonly' => false,
676 'nocreate' => false,
677 'watch' => [
678 ApiBase::PARAM_DFLT => false,
680 ],
681 'unwatch' => [
682 ApiBase::PARAM_DFLT => false,
684 ],
685 ];
686
687 // Params appear in the docs in the order they are defined,
688 // which is why this is here and not at the bottom.
689 $params += $this->getWatchlistParams();
690
691 return $params + [
692 'md5' => null,
693 'prependtext' => [
694 ApiBase::PARAM_TYPE => 'text',
695 ],
696 'appendtext' => [
697 ApiBase::PARAM_TYPE => 'text',
698 ],
699 'undo' => [
700 ApiBase::PARAM_TYPE => 'integer',
703 ],
704 'undoafter' => [
705 ApiBase::PARAM_TYPE => 'integer',
708 ],
709 'redirect' => [
710 ApiBase::PARAM_TYPE => 'boolean',
711 ApiBase::PARAM_DFLT => false,
712 ],
713 'contentformat' => [
714 ApiBase::PARAM_TYPE => $this->contentHandlerFactory->getAllContentFormats(),
715 ],
716 'contentmodel' => [
717 ApiBase::PARAM_TYPE => $this->contentHandlerFactory->getContentModels(),
718 ],
719 'token' => [
720 // Standard definition automatically inserted
721 ApiBase::PARAM_HELP_MSG_APPEND => [ 'apihelp-edit-param-token' ],
722 ],
723 ];
724 }
725
726 public function needsToken() {
727 return 'csrf';
728 }
729
730 protected function getExamplesMessages() {
731 return [
732 'action=edit&title=Test&summary=test%20summary&' .
733 'text=article%20content&baserevid=1234567&token=123ABC'
734 => 'apihelp-edit-example-edit',
735 'action=edit&title=Test&summary=NOTOC&minor=&' .
736 'prependtext=__NOTOC__%0A&basetimestamp=2007-08-24T12:34:54Z&token=123ABC'
737 => 'apihelp-edit-example-prepend',
738 'action=edit&title=Test&undo=13585&undoafter=13579&' .
739 'basetimestamp=2007-08-24T12:34:54Z&token=123ABC'
740 => 'apihelp-edit-example-undo',
741 ];
742 }
743
744 public function getHelpUrls() {
745 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Edit';
746 }
747}
getWatchlistValue(string $watchlist, Title $title, User $user, ?string $userOption=null)
Return true if we're to watch the page, false if not.
getExpiryFromParams(array $params)
Get formatted expiry from the given parameters, or null if no expiry was provided.
getWatchlistExpiry(WatchedItemStoreInterface $store, Title $title, UserIdentity $user)
Get existing expiry from the database.
getWatchlistParams(array $watchOptions=[])
Get additional allow params specific to watchlisting.
WatchlistManager $watchlistManager
const NS_MEDIAWIKI
Definition Defines.php:72
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
$wgRequest
Definition Setup.php:702
$wgTitle
Definition Setup.php:849
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:55
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1436
const PARAM_DEPRECATED
Definition ApiBase.php:101
getMain()
Get the main module.
Definition ApiBase.php:513
const PARAM_TYPE
Definition ApiBase.php:81
getErrorFormatter()
Definition ApiBase.php:639
const PARAM_DFLT
Definition ApiBase.php:73
const PARAM_HELP_MSG_APPEND
((string|array|Message)[]) Specify additional i18n messages to append to the normal message for this ...
Definition ApiBase.php:169
dieReadOnly()
Helper function for readonly errors.
Definition ApiBase.php:1523
const PARAM_MIN
Definition ApiBase.php:93
requireAtLeastOneParameter( $params,... $required)
Die if none of a certain set of parameters is set and not false.
Definition ApiBase.php:961
getResult()
Get the result object.
Definition ApiBase.php:628
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:764
const PARAM_RANGE_ENFORCE
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition ApiBase.php:152
checkTitleUserPermissions( $pageIdentity, $actions, array $options=[])
Helper function for permission-denied errors.
Definition ApiBase.php:1565
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:497
getTitleOrPageId( $params, $load=false)
Get a WikiPage object from a title or pageid param, if possible.
Definition ApiBase.php:1033
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition ApiBase.php:1495
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition ApiBase.php:1293
dieBlocked(Block $block)
Throw an ApiUsageException, which will (if uncaught) call the main module's error handler and die wit...
Definition ApiBase.php:1463
dieWithException(Throwable $exception, array $options=[])
Abort execution with an error derived from a throwable.
Definition ApiBase.php:1448
const PARAM_ISMULTI
Definition ApiBase.php:77
A module that allows for editing and creating pages.
WatchedItemStoreInterface $watchedItemStore
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
needsToken()
Returns the token type this module requires in order to execute.
isWriteMode()
Indicates whether this module requires write mode.
__construct(ApiMain $mainModule, $moduleName, IContentHandlerFactory $contentHandlerFactory=null, RevisionLookup $revisionLookup=null, WatchedItemStoreInterface $watchedItemStore=null, WikiPageFactory $wikiPageFactory=null, WatchlistManager $watchlistManager=null, UserOptionsLookup $userOptionsLookup=null)
mustBePosted()
Indicates whether this module must be called with a POST request.
UserOptionsLookup $userOptionsLookup
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
RevisionLookup $revisionLookup
getExamplesMessages()
Returns usage examples for this module.
WikiPageFactory $wikiPageFactory
IContentHandlerFactory $contentHandlerFactory
getHelpUrls()
Return links to more detailed help pages about the module.
This is the main API class, used for both external and internal processing.
Definition ApiMain.php:49
static canAddTagsAccompanyingChange(array $tags, Authority $performer=null)
Is it OK to allow the user to apply all the specified tags at the same time as they edit/make the cha...
Similar to FauxRequest, but only fakes URL parameters and method (POST or GET) and use the base reque...
The edit page/HTML interface (split from Article) The actual database and text munging is still in Ar...
Definition EditPage.php:88
const UNICODE_CHECK
Used for Unicode support checks.
Definition EditPage.php:95
Exception representing a failure to serialize or unserialize a content object.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Page revision base class.
Value object representing a content slot associated with a page revision.
Provides access to user options.
static plaintextParam( $plaintext)
Definition Message.php:1200
Group all the pieces relevant to the context of a request into one instance @newable.
setRequest(WebRequest $request)
Content object implementation for representing flat text.
Represents a title within MediaWiki.
Definition Title.php:48
trait ApiWatchlistTrait
An ApiWatchlistTrait adds class properties and convenience methods for APIs that allow you to watch a...
const AS_RATE_LIMITED
Status: rate limiter for action 'edit' was tripped.
const AS_NO_CHANGE_CONTENT_MODEL
Status: user tried to modify the content model, but is not allowed to do that ( User::isAllowed('edit...
const AS_READ_ONLY_PAGE_LOGGED
Status: this logged in user is not allowed to edit this page.
const AS_READ_ONLY_PAGE_ANON
Status: this anonymous user is not allowed to edit this page.
const AS_CONFLICT_DETECTED
Status: (non-resolvable) edit conflict.
const AS_ARTICLE_WAS_DELETED
Status: article was deleted while editing and wpRecreate == false or form was not posted.
const AS_TEXTBOX_EMPTY
Status: user tried to create a new section without content.
const AS_HOOK_ERROR_EXPECTED
Status: A hook function returned an error.
const AS_CONTENT_TOO_BIG
Status: Content too big (> $wgMaxArticleSize)
const AS_SPAM_ERROR
Status: summary contained spam according to one of the regexes in $wgSummarySpamRegex.
const AS_SUCCESS_UPDATE
Status: Article successfully updated.
const AS_IMAGE_REDIRECT_ANON
Status: anonymous user is not allowed to upload (User::isAllowed('upload') == false)
const AS_SUCCESS_NEW_ARTICLE
Status: Article successfully created.
const AS_NO_CREATE_PERMISSION
Status: user tried to create this page, but is not allowed to do that.
const AS_IMAGE_REDIRECT_LOGGED
Status: logged in user is not allowed to upload (User::isAllowed('upload') == false)
const AS_MAX_ARTICLE_SIZE_EXCEEDED
Status: article is too big (> $wgMaxArticleSize), after merging in the new section.
const AS_BLOCKED_PAGE_FOR_USER
Status: User is blocked from editing this page.
const AS_SUMMARY_NEEDED
Status: no edit summary given and the user has forceeditsummary set and the user is not editing in hi...
const AS_HOOK_ERROR
Status: Article update aborted by a hook function.
const AS_BLANK_ARTICLE
Status: user tried to create a blank page and wpIgnoreBlankArticle == false.
const AS_READ_ONLY_PAGE
Status: wiki is in readonly mode (wfReadOnly() == true)
Service for looking up page revisions.
$content
Definition router.php:76