MediaWiki REL1_35
UpdateHandler.php
Go to the documentation of this file.
1<?php
2
4
5use FormatJson;
12use TextContent;
15
20
25
29 protected function getTitleParameter() {
30 return $this->getValidatedParams()['title'];
31 }
32
38 public function setJsonDiffFunction( callable $jsonDiffFunction ) {
39 $this->jsonDiffFunction = $jsonDiffFunction;
40 }
41
45 public function getParamSettings() {
46 return [
47 'title' => [
48 self::PARAM_SOURCE => 'path',
49 ParamValidator::PARAM_TYPE => 'string',
50 ParamValidator::PARAM_REQUIRED => true,
51 ],
52 ];
53 }
54
58 public function getBodyValidator( $contentType ) {
59 if ( $contentType !== 'application/json' ) {
60 throw new HttpException( "Unsupported Content-Type",
61 415,
62 [ 'content_type' => $contentType ]
63 );
64 }
65
66 return new JsonBodyValidator( [
67 'source' => [
68 self::PARAM_SOURCE => 'body',
69 ParamValidator::PARAM_TYPE => 'string',
70 ParamValidator::PARAM_REQUIRED => true,
71 ],
72 'comment' => [
73 self::PARAM_SOURCE => 'body',
74 ParamValidator::PARAM_TYPE => 'string',
75 ParamValidator::PARAM_REQUIRED => true,
76 ],
77 'content_model' => [
78 self::PARAM_SOURCE => 'body',
79 ParamValidator::PARAM_TYPE => 'string',
80 ParamValidator::PARAM_REQUIRED => false,
81 ],
82 'latest' => [
83 self::PARAM_SOURCE => 'body',
84 ParamValidator::PARAM_TYPE => 'array',
85 ParamValidator::PARAM_REQUIRED => false,
86 ],
87 'token' => [
88 self::PARAM_SOURCE => 'body',
89 ParamValidator::PARAM_TYPE => 'string',
90 ParamValidator::PARAM_REQUIRED => false,
91 ParamValidator::PARAM_DEFAULT => '',
92 ],
93 ] );
94 }
95
99 protected function getActionModuleParameters() {
100 $body = $this->getValidatedBody();
101
102 $title = $this->getTitleParameter();
103 $baseRevId = $body['latest']['id'] ?? 0;
104
105 $contentmodel = $body['content_model'] ?: null;
106
107 if ( $contentmodel !== null && !$this->contentHandlerFactory->isDefinedModel( $contentmodel ) ) {
108 throw new LocalizedHttpException(
109 new MessageValue( 'rest-bad-content-model', [ $contentmodel ] ), 400
110 );
111 }
112
113 $token = $this->getActionModuleToken();
114
115 $params = [
116 'action' => 'edit',
117 'title' => $title,
118 'text' => $body['source'],
119 'summary' => $body['comment'],
120 'token' => $token
121 ];
122
123 if ( $contentmodel !== null ) {
124 $params['contentmodel'] = $contentmodel;
125 }
126
127 if ( $baseRevId > 0 ) {
128 $params['baserevid'] = $baseRevId;
129 $params['nocreate'] = true;
130 } else {
131 $params['createonly'] = true;
132 }
133
134 return $params;
135 }
136
140 protected function throwHttpExceptionForActionModuleError( IApiMessage $msg, $statusCode = 400 ) {
141 $code = $msg->getApiCode();
142
143 // Provide a message instructing the client to provide the base revision ID for updates.
144 if ( $code === 'articleexists' ) {
145 $title = $this->getTitleParameter();
146 throw new LocalizedHttpException(
147 new MessageValue( 'rest-update-cannot-create-page', [ $title ] ),
148 409
149 );
150 }
151
152 if ( $code === 'editconflict' ) {
153 $data = $this->getConflictData();
154 throw new LocalizedHttpException( $this->makeMessageValue( $msg ), 409, $data );
155 }
156
157 parent::throwHttpExceptionForActionModuleError( $msg, $statusCode );
158 }
159
173 private function getConflictData() {
174 $body = $this->getValidatedBody();
175 $baseRevId = $body['latest']['id'] ?? 0;
176 $title = $this->titleParser->parseTitle( $this->getValidatedParams()['title'] );
177
178 $baseRev = $this->revisionLookup->getRevisionById( $baseRevId );
179 $currentRev = $this->revisionLookup->getRevisionByTitle( $title );
180
181 if ( !$baseRev || !$currentRev ) {
182 return [];
183 }
184
185 $baseContent = $baseRev->getContent(
186 SlotRecord::MAIN,
187 RevisionRecord::FOR_THIS_USER,
188 $this->getUser()
189 );
190 $currentContent = $currentRev->getContent(
191 SlotRecord::MAIN,
192 RevisionRecord::FOR_THIS_USER,
193 $this->getUser()
194 );
195
196 if ( !$baseContent || !$currentContent ) {
197 return [];
198 }
199
200 $model = $body['content_model'] ?: $baseContent->getModel();
201 $contentHandler = $this->contentHandlerFactory->getContentHandler( $model );
202 $newContent = $contentHandler->unserializeContent( $body['source'] );
203
204 if ( !$baseContent instanceof TextContent
205 || !$currentContent instanceof TextContent
206 || !$newContent instanceof TextContent
207 ) {
208 return [];
209 }
210
211 $localDiff = $this->getDiff( $baseContent, $newContent );
212 $remoteDiff = $this->getDiff( $baseContent, $currentContent );
213
214 if ( !$localDiff || !$remoteDiff ) {
215 return [];
216 }
217
218 return [
219 'base' => $baseRev->getId(),
220 'current' => $currentRev->getId(),
221 'local' => $localDiff,
222 'remote' => $remoteDiff,
223 ];
224 }
225
234 private function getDiff( TextContent $from, TextContent $to ) {
235 if ( !is_callable( $this->jsonDiffFunction ) ) {
236 return null;
237 }
238
239 $json = ( $this->jsonDiffFunction )( $from->getText(), $to->getText(), 2 );
241 }
242}
JSON formatter wrapper class.
const FORCE_ASSOC
If set, treat JSON objects '{...}' as associative arrays.
static decode( $value, $assoc=false)
Decodes a JSON string.
makeMessageValue(IApiMessage $msg)
Constructs a MessageValue from an IApiMessage.
Base class for REST API handlers that perform page edits (main slot only).
getActionModuleToken()
Determines the CSRF token to be passed to the action module.
Core REST API endpoint that handles page updates (main slot only)
setJsonDiffFunction(callable $jsonDiffFunction)
Sets the function to use for JSON diffs, for testing.
getTitleParameter()
Returns the requested title.string
getConflictData()
Returns an associative array to be used in the response in the event of edit conflicts.
getActionModuleParameters()
Maps a REST API request to an action API request.Implementations typically use information returned b...
getDiff(TextContent $from, TextContent $to)
Returns a text diff encoded as an array, to be included in the response data.
throwHttpExceptionForActionModuleError(IApiMessage $msg, $statusCode=400)
Throws a HttpException for a given IApiMessage that represents an error.Never returns normally....
getBodyValidator( $contentType)
Fetch the BodyValidator.Stable to overrideBodyValidator
getParamSettings()
Fetch ParamValidator settings for parameters.Every setting must include self::PARAM_SOURCE to specify...
getValidatedBody()
Fetch the validated body.
Definition Handler.php:269
getValidatedParams()
Fetch the validated parameters.
Definition Handler.php:257
This is the base exception class for non-fatal exceptions thrown from REST handlers.
Page revision base class.
Value object representing a content slot associated with a page revision.
Content object implementation for representing flat text.
getText()
Returns the text represented by this Content object, as a string.
Value object representing a message for i18n.
Service for formatting and validating API parameters.
Interface for messages with machine-readable data for use by the API.
getApiCode()
Returns a machine-readable code for use by the API.