MediaWiki  master
ApiStashEdit.php
Go to the documentation of this file.
1 <?php
27 
41 class ApiStashEdit extends ApiBase {
42 
45 
47  private $pageEditStash;
48 
50  private $revisionLookup;
51 
54 
57 
67  public function __construct(
68  ApiMain $main,
69  $action,
75  ) {
76  parent::__construct( $main, $action );
77 
78  $this->contentHandlerFactory = $contentHandlerFactory;
79  $this->pageEditStash = $pageEditStash;
80  $this->revisionLookup = $revisionLookup;
81  $this->statsdDataFactory = $statsdDataFactory;
82  $this->wikiPageFactory = $wikiPageFactory;
83  }
84 
85  public function execute() {
86  $user = $this->getUser();
87  $params = $this->extractRequestParams();
88 
89  if ( $user->isBot() ) { // sanity
90  $this->dieWithError( 'apierror-botsnotsupported' );
91  }
92 
93  $page = $this->getTitleOrPageId( $params );
94  $title = $page->getTitle();
95  $this->getErrorFormatter()->setContextTitle( $title );
96 
97  if ( !$this->contentHandlerFactory
98  ->getContentHandler( $params['contentmodel'] )
99  ->isSupportedFormat( $params['contentformat'] )
100  ) {
101  $this->dieWithError(
102  [ 'apierror-badformat-generic', $params['contentformat'], $params['contentmodel'] ],
103  'badmodelformat'
104  );
105  }
106 
107  $this->requireOnlyOneParameter( $params, 'stashedtexthash', 'text' );
108 
109  $text = null;
110  $textHash = null;
111  if ( $params['stashedtexthash'] !== null ) {
112  // Load from cache since the client indicates the text is the same as last stash
113  $textHash = $params['stashedtexthash'];
114  if ( !preg_match( '/^[0-9a-f]{40}$/', $textHash ) ) {
115  $this->dieWithError( 'apierror-stashedit-missingtext', 'missingtext' );
116  }
117  $text = $this->pageEditStash->fetchInputText( $textHash );
118  if ( !is_string( $text ) ) {
119  $this->dieWithError( 'apierror-stashedit-missingtext', 'missingtext' );
120  }
121  } else {
122  // 'text' was passed. Trim and fix newlines so the key SHA1's
123  // match (see WebRequest::getText())
124  $text = rtrim( str_replace( "\r\n", "\n", $params['text'] ) );
125  $textHash = sha1( $text );
126  }
127 
128  $textContent = ContentHandler::makeContent(
129  $text, $title, $params['contentmodel'], $params['contentformat'] );
130 
131  $page = $this->wikiPageFactory->newFromTitle( $title );
132  if ( $page->exists() ) {
133  // Page exists: get the merged content with the proposed change
134  $baseRev = $this->revisionLookup->getRevisionByPageId(
135  $page->getId(),
136  $params['baserevid']
137  );
138  if ( !$baseRev ) {
139  $this->dieWithError( [ 'apierror-nosuchrevid', $params['baserevid'] ] );
140  }
141  $currentRev = $page->getRevisionRecord();
142  if ( !$currentRev ) {
143  $this->dieWithError( [ 'apierror-missingrev-pageid', $page->getId() ], 'missingrev' );
144  }
145  // Merge in the new version of the section to get the proposed version
146  $editContent = $page->replaceSectionAtRev(
147  $params['section'],
148  $textContent,
149  $params['sectiontitle'],
150  $baseRev->getId()
151  );
152  if ( !$editContent ) {
153  $this->dieWithError( 'apierror-sectionreplacefailed', 'replacefailed' );
154  }
155  if ( $currentRev->getId() == $baseRev->getId() ) {
156  // Base revision was still the latest; nothing to merge
157  $content = $editContent;
158  } else {
159  // Merge the edit into the current version
160  $baseContent = $baseRev->getContent( SlotRecord::MAIN );
161  $currentContent = $currentRev->getContent( SlotRecord::MAIN );
162  if ( !$baseContent || !$currentContent ) {
163  $this->dieWithError( [ 'apierror-missingcontent-pageid', $page->getId() ], 'missingrev' );
164  }
165 
166  $baseModel = $baseContent->getModel();
167  $currentModel = $currentContent->getModel();
168 
169  // T255700: Put this in try-block because if the models of these three Contents
170  // happen to not be identical, the ContentHandler may throw exception here.
171  try {
172  $content = $this->contentHandlerFactory
173  ->getContentHandler( $baseModel )
174  ->merge3( $baseContent, $editContent, $currentContent );
175  } catch ( Exception $e ) {
176  $this->dieWithException( $e, [
177  'wrap' => ApiMessage::create(
178  [ 'apierror-contentmodel-mismatch', $currentModel, $baseModel ]
179  )
180  ] );
181  }
182 
183  }
184  } else {
185  // New pages: use the user-provided content model
186  $content = $textContent;
187  }
188 
189  if ( !$content ) { // merge3() failed
190  $this->getResult()->addValue( null,
191  $this->getModuleName(), [ 'status' => 'editconflict' ] );
192  return;
193  }
194 
195  if ( $user->pingLimiter( 'stashedit' ) ) {
196  $status = 'ratelimited';
197  } else {
198  $status = $this->pageEditStash->parseAndCache( $page, $content, $user, $params['summary'] );
199  $this->pageEditStash->stashInputText( $text, $textHash );
200  }
201 
202  $this->statsdDataFactory->increment( "editstash.cache_stores.$status" );
203 
204  $ret = [ 'status' => $status ];
205  // If we were rate-limited, we still return the pre-existing valid hash if one was passed
206  if ( $status !== 'ratelimited' || $params['stashedtexthash'] !== null ) {
207  $ret['texthash'] = $textHash;
208  }
209 
210  $this->getResult()->addValue( null, $this->getModuleName(), $ret );
211  }
212 
222  public function parseAndStash( WikiPage $page, Content $content, UserIdentity $user, $summary ) {
223  return $this->pageEditStash->parseAndCache( $page, $content, $user, $summary ?? '' );
224  }
225 
226  public function getAllowedParams() {
227  return [
228  'title' => [
229  ApiBase::PARAM_TYPE => 'string',
231  ],
232  'section' => [
233  ApiBase::PARAM_TYPE => 'string',
234  ],
235  'sectiontitle' => [
236  ApiBase::PARAM_TYPE => 'string'
237  ],
238  'text' => [
239  ApiBase::PARAM_TYPE => 'text',
240  ApiBase::PARAM_DFLT => null
241  ],
242  'stashedtexthash' => [
243  ApiBase::PARAM_TYPE => 'string',
244  ApiBase::PARAM_DFLT => null
245  ],
246  'summary' => [
247  ApiBase::PARAM_TYPE => 'string',
248  ApiBase::PARAM_DFLT => ''
249  ],
250  'contentmodel' => [
251  ApiBase::PARAM_TYPE => $this->contentHandlerFactory->getContentModels(),
253  ],
254  'contentformat' => [
255  ApiBase::PARAM_TYPE => $this->contentHandlerFactory->getAllContentFormats(),
257  ],
258  'baserevid' => [
259  ApiBase::PARAM_TYPE => 'integer',
261  ]
262  ];
263  }
264 
265  public function needsToken() {
266  return 'csrf';
267  }
268 
269  public function mustBePosted() {
270  return true;
271  }
272 
273  public function isWriteMode() {
274  return true;
275  }
276 
277  public function isInternal() {
278  return true;
279  }
280 }
ApiMain
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:49
ApiStashEdit\__construct
__construct(ApiMain $main, $action, IContentHandlerFactory $contentHandlerFactory, PageEditStash $pageEditStash, RevisionLookup $revisionLookup, IBufferingStatsdDataFactory $statsdDataFactory, WikiPageFactory $wikiPageFactory)
Definition: ApiStashEdit.php:67
ApiStashEdit\needsToken
needsToken()
Returns the token type this module requires in order to execute.
Definition: ApiStashEdit.php:265
ApiBase\PARAM_REQUIRED
const PARAM_REQUIRED
Definition: ApiBase.php:105
ApiStashEdit\isWriteMode
isWriteMode()
Indicates whether this module requires write mode.
Definition: ApiStashEdit.php:273
ApiStashEdit\$wikiPageFactory
WikiPageFactory $wikiPageFactory
Definition: ApiStashEdit.php:56
ApiBase\dieWithError
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:1436
true
return true
Definition: router.php:90
ApiBase\getTitleOrPageId
getTitleOrPageId( $params, $load=false)
Get a WikiPage object from a title or pageid param, if possible.
Definition: ApiBase.php:1033
ApiBase\PARAM_TYPE
const PARAM_TYPE
Definition: ApiBase.php:81
ApiBase\getResult
getResult()
Get the result object.
Definition: ApiBase.php:628
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:60
ApiStashEdit\execute
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
Definition: ApiStashEdit.php:85
ContextSource\getUser
getUser()
Definition: ContextSource.php:136
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:39
MediaWiki\Revision\RevisionLookup
Service for looking up page revisions.
Definition: RevisionLookup.php:38
ApiStashEdit\$revisionLookup
RevisionLookup $revisionLookup
Definition: ApiStashEdit.php:50
ApiBase
This abstract class implements many basic API functions, and is the base of all API classes.
Definition: ApiBase.php:55
MediaWiki\Storage\PageEditStash
Class for managing stashed edits used by the page updater classes.
Definition: PageEditStash.php:48
ApiStashEdit\$pageEditStash
PageEditStash $pageEditStash
Definition: ApiStashEdit.php:47
Page\WikiPageFactory
Definition: WikiPageFactory.php:20
ApiBase\extractRequestParams
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:764
$title
$title
Definition: testCompression.php:38
ApiMessage\create
static create( $msg, $code=null, array $data=null)
Create an IApiMessage for the message.
Definition: ApiMessage.php:43
ContentHandler\makeContent
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
Definition: ContentHandler.php:147
ApiStashEdit\getAllowedParams
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiStashEdit.php:226
$content
$content
Definition: router.php:76
ApiBase\requireOnlyOneParameter
requireOnlyOneParameter( $params,... $required)
Die if none or more than one of a certain set of parameters is set and not false.
Definition: ApiBase.php:901
MediaWiki\Content\IContentHandlerFactory
Definition: IContentHandlerFactory.php:10
ApiStashEdit\isInternal
isInternal()
Indicates whether this module is "internal" Internal API modules are not (yet) intended for 3rd party...
Definition: ApiStashEdit.php:277
ApiStashEdit\parseAndStash
parseAndStash(WikiPage $page, Content $content, UserIdentity $user, $summary)
Definition: ApiStashEdit.php:222
ApiStashEdit\$contentHandlerFactory
IContentHandlerFactory $contentHandlerFactory
Definition: ApiStashEdit.php:44
ApiStashEdit\$statsdDataFactory
IBufferingStatsdDataFactory $statsdDataFactory
Definition: ApiStashEdit.php:53
Content
Base interface for content objects.
Definition: Content.php:35
IBufferingStatsdDataFactory
MediaWiki adaptation of StatsdDataFactory that provides buffering functionality.
Definition: IBufferingStatsdDataFactory.php:13
ApiBase\PARAM_DFLT
const PARAM_DFLT
Definition: ApiBase.php:73
ApiBase\dieWithException
dieWithException(Throwable $exception, array $options=[])
Abort execution with an error derived from a throwable.
Definition: ApiBase.php:1449
ApiBase\getModuleName
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:497
ApiStashEdit
Prepare an edit in shared cache so that it can be reused on edit.
Definition: ApiStashEdit.php:41
ApiBase\getErrorFormatter
getErrorFormatter()
Definition: ApiBase.php:639
MediaWiki\Revision\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:40
ApiStashEdit\mustBePosted
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition: ApiStashEdit.php:269