MediaWiki  master
ApiStashEdit.php
Go to the documentation of this file.
1 <?php
23 
37 class 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' => [
194  ],
195  'contentformat' => [
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 }
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:142
const PARAM_TYPE
(string|string[]) Either an array of allowed value strings, or a string type as described below...
Definition: ApiBase.php:94
getResult()
Get the result object.
Definition: ApiBase.php:640
Prepare an edit in shared cache so that it can be reused on edit.
static getAllContentFormats()
const PARAM_DFLT
(null|boolean|integer|string) Default value of the parameter.
Definition: ApiBase.php:55
static getContentModels()
parseAndStash(WikiPage $page, Content $content, User $user, $summary)
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:2006
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user...
Definition: ApiBase.php:761
static newFromPageId( $pageId, $revId=0, $flags=0)
Load either the current, or a specified, revision that&#39;s attached to a given page ID...
Definition: Revision.php:158
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
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
static getForModelID( $modelId)
Returns the ContentHandler singleton for the given model ID.
const ERROR_BUSY
const ERROR_NONE
const ERROR_PARSE
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:520
const ERROR_CACHE
getTitleOrPageId( $params, $load=false)
Get a WikiPage object from a title or pageid param, if possible.
Definition: ApiBase.php:1025
const ERROR_UNCACHEABLE
This abstract class implements many basic API functions, and is the base of all API classes...
Definition: ApiBase.php:42
$content
Definition: router.php:78
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
return true
Definition: router.php:92