MediaWiki REL1_34
ApiStashEdit.php
Go to the documentation of this file.
1<?php
23
37class ApiStashEdit extends ApiBase {
38 const ERROR_NONE = PageEditStash::ERROR_NONE; // b/c
39 const ERROR_PARSE = PageEditStash::ERROR_PARSE; // b/c
40 const ERROR_CACHE = PageEditStash::ERROR_CACHE; // b/c
41 const ERROR_UNCACHEABLE = PageEditStash::ERROR_UNCACHEABLE; // b/c
42 const ERROR_BUSY = PageEditStash::ERROR_BUSY; // b/c
43
44 public function execute() {
45 $user = $this->getUser();
46 $params = $this->extractRequestParams();
47
48 if ( $user->isBot() ) { // sanity
49 $this->dieWithError( 'apierror-botsnotsupported' );
50 }
51
52 $editStash = MediaWikiServices::getInstance()->getPageEditStash();
53 $page = $this->getTitleOrPageId( $params );
54 $title = $page->getTitle();
55
56 if ( !ContentHandler::getForModelID( $params['contentmodel'] )
57 ->isSupportedFormat( $params['contentformat'] )
58 ) {
59 $this->dieWithError(
60 [ 'apierror-badformat-generic', $params['contentformat'], $params['contentmodel'] ],
61 'badmodelformat'
62 );
63 }
64
65 $this->requireOnlyOneParameter( $params, 'stashedtexthash', 'text' );
66
67 $text = null;
68 $textHash = null;
69 if ( $params['stashedtexthash'] !== null ) {
70 // Load from cache since the client indicates the text is the same as last stash
71 $textHash = $params['stashedtexthash'];
72 if ( !preg_match( '/^[0-9a-f]{40}$/', $textHash ) ) {
73 $this->dieWithError( 'apierror-stashedit-missingtext', 'missingtext' );
74 }
75 $text = $editStash->fetchInputText( $textHash );
76 if ( !is_string( $text ) ) {
77 $this->dieWithError( 'apierror-stashedit-missingtext', 'missingtext' );
78 }
79 } else {
80 // 'text' was passed. Trim and fix newlines so the key SHA1's
81 // match (see WebRequest::getText())
82 $text = rtrim( str_replace( "\r\n", "\n", $params['text'] ) );
83 $textHash = sha1( $text );
84 }
85
86 $textContent = ContentHandler::makeContent(
87 $text, $title, $params['contentmodel'], $params['contentformat'] );
88
89 $page = WikiPage::factory( $title );
90 if ( $page->exists() ) {
91 // Page exists: get the merged content with the proposed change
92 $baseRev = Revision::newFromPageId( $page->getId(), $params['baserevid'] );
93 if ( !$baseRev ) {
94 $this->dieWithError( [ 'apierror-nosuchrevid', $params['baserevid'] ] );
95 }
96 $currentRev = $page->getRevision();
97 if ( !$currentRev ) {
98 $this->dieWithError( [ 'apierror-missingrev-pageid', $page->getId() ], 'missingrev' );
99 }
100 // Merge in the new version of the section to get the proposed version
101 $editContent = $page->replaceSectionAtRev(
102 $params['section'],
103 $textContent,
104 $params['sectiontitle'],
105 $baseRev->getId()
106 );
107 if ( !$editContent ) {
108 $this->dieWithError( 'apierror-sectionreplacefailed', 'replacefailed' );
109 }
110 if ( $currentRev->getId() == $baseRev->getId() ) {
111 // Base revision was still the latest; nothing to merge
112 $content = $editContent;
113 } else {
114 // Merge the edit into the current version
115 $baseContent = $baseRev->getContent();
116 $currentContent = $currentRev->getContent();
117 if ( !$baseContent || !$currentContent ) {
118 $this->dieWithError( [ 'apierror-missingcontent-pageid', $page->getId() ], 'missingrev' );
119 }
120 $handler = ContentHandler::getForModelID( $baseContent->getModel() );
121 $content = $handler->merge3( $baseContent, $editContent, $currentContent );
122 }
123 } else {
124 // New pages: use the user-provided content model
125 $content = $textContent;
126 }
127
128 if ( !$content ) { // merge3() failed
129 $this->getResult()->addValue( null,
130 $this->getModuleName(), [ 'status' => 'editconflict' ] );
131 return;
132 }
133
134 if ( $user->pingLimiter( 'stashedit' ) ) {
135 $status = 'ratelimited';
136 } else {
137 $status = $editStash->parseAndCache( $page, $content, $user, $params['summary'] );
138 $editStash->stashInputText( $text, $textHash );
139 }
140
141 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
142 $stats->increment( "editstash.cache_stores.$status" );
143
144 $ret = [ 'status' => $status ];
145 // If we were rate-limited, we still return the pre-existing valid hash if one was passed
146 if ( $status !== 'ratelimited' || $params['stashedtexthash'] !== null ) {
147 $ret['texthash'] = $textHash;
148 }
149
150 $this->getResult()->addValue( null, $this->getModuleName(), $ret );
151 }
152
162 public function parseAndStash( WikiPage $page, Content $content, User $user, $summary ) {
163 $editStash = MediaWikiServices::getInstance()->getPageEditStash();
164
165 return $editStash->parseAndCache( $page, $content, $user, $summary );
166 }
167
168 public function getAllowedParams() {
169 return [
170 'title' => [
171 ApiBase::PARAM_TYPE => 'string',
173 ],
174 'section' => [
175 ApiBase::PARAM_TYPE => 'string',
176 ],
177 'sectiontitle' => [
178 ApiBase::PARAM_TYPE => 'string'
179 ],
180 'text' => [
181 ApiBase::PARAM_TYPE => 'text',
182 ApiBase::PARAM_DFLT => null
183 ],
184 'stashedtexthash' => [
185 ApiBase::PARAM_TYPE => 'string',
186 ApiBase::PARAM_DFLT => null
187 ],
188 'summary' => [
189 ApiBase::PARAM_TYPE => 'string',
190 ],
191 'contentmodel' => [
192 ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
194 ],
195 'contentformat' => [
196 ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
198 ],
199 'baserevid' => [
200 ApiBase::PARAM_TYPE => 'integer',
202 ]
203 ];
204 }
205
206 public function needsToken() {
207 return 'csrf';
208 }
209
210 public function mustBePosted() {
211 return true;
212 }
213
214 public function isWriteMode() {
215 return true;
216 }
217
218 public function isInternal() {
219 return true;
220 }
221}
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:42
const PARAM_REQUIRED
(boolean) Is the parameter required?
Definition ApiBase.php:118
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition ApiBase.php:2014
const PARAM_TYPE
(string|string[]) Either an array of allowed value strings, or a string type as described below.
Definition ApiBase.php:94
const PARAM_DFLT
(null|boolean|integer|string) Default value of the parameter.
Definition ApiBase.php:55
getResult()
Get the result object.
Definition ApiBase.php:640
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:761
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:520
getTitleOrPageId( $params, $load=false)
Get a WikiPage object from a title or pageid param, if possible.
Definition ApiBase.php:1034
requireOnlyOneParameter( $params, $required)
Die if none or more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:893
Prepare an edit in shared cache so that it can be reused on edit.
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
const ERROR_UNCACHEABLE
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
isInternal()
Indicates whether this module is "internal" Internal API modules are not (yet) intended for 3rd party...
needsToken()
Returns the token type this module requires in order to execute.
isWriteMode()
Indicates whether this module requires write mode.
mustBePosted()
Indicates whether this module must be called with a POST request.
parseAndStash(WikiPage $page, Content $content, User $user, $summary)
MediaWikiServices is the service locator for the application scope of MediaWiki.
Class for managing stashed edits used by the page updater classes.
static newFromPageId( $pageId, $revId=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given page ID.
Definition Revision.php:157
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:51
Class representing a MediaWiki article and history.
Definition WikiPage.php:47
Base interface for content objects.
Definition Content.php:34
$content
Definition router.php:78
return true
Definition router.php:94