MediaWiki REL1_35
EditHandler.php
Go to the documentation of this file.
1<?php
2
4
5use Config;
14use TitleParser;
15use WebResponse;
17
21abstract class EditHandler extends ActionModuleBasedHandler {
22
24 protected $config;
25
30
34 protected $titleParser;
35
39 protected $titleFormatter;
40
44 protected $revisionLookup;
45
53 public function __construct(
59 ) {
60 $this->config = $config;
61 $this->contentHandlerFactory = $contentHandlerFactory;
62 $this->titleParser = $titleParser;
63 $this->titleFormatter = $titleFormatter;
64 $this->revisionLookup = $revisionLookup;
65 }
66
67 public function needsWriteAccess() {
68 return true;
69 }
70
76 abstract protected function getTitleParameter();
77
81 protected function mapActionModuleResult( array $data ) {
82 if ( isset( $data['error'] ) ) {
83 throw new LocalizedHttpException( new MessageValue( 'apierror-' . $data['error'] ), 400 );
84 }
85
86 if ( !isset( $data['edit'] ) || !$data['edit']['result'] ) {
87 throw new HttpException( 'Bad result structure received from ApiEditPage' );
88 }
89
90 if ( $data['edit']['result'] !== 'Success' ) {
91 // Probably an edit conflict
92 // TODO: which code for null edits?
93 throw new HttpException( $data['edit']['result'], 409 );
94 }
95
96 $title = $this->titleParser->parseTitle( $data['edit']['title'] );
97
98 // This seems wasteful. This is the downside of delegating to the action API module:
99 // if we need additional data in the response, we have to load it.
100 $revision = $this->revisionLookup->getRevisionById( (int)$data['edit']['newrevid'] );
101 $content = $revision->getContent( SlotRecord::MAIN );
102
103 return [
104 'id' => $data['edit']['pageid'],
105 'title' => $this->titleFormatter->getPrefixedText( $title ),
106 'key' => $this->titleFormatter->getPrefixedDBkey( $title ),
107 'latest' => [
108 'id' => $data['edit']['newrevid'],
109 'timestamp' => $data['edit']['newtimestamp'],
110 ],
111 'license' => [
112 'url' => $this->config->get( 'RightsUrl' ),
113 'title' => $this->config->get( 'RightsText' )
114 ],
115 'content_model' => $data['edit']['contentmodel'],
116 'source' => $content->serialize(),
117 ];
118 }
119
123 protected function throwHttpExceptionForActionModuleError( IApiMessage $msg, $statusCode = 400 ) {
124 $code = $msg->getApiCode();
125
126 if ( $code === 'protectedpage' ) {
127 throw new LocalizedHttpException( $this->makeMessageValue( $msg ), 403 );
128 }
129
130 if ( $code === 'badtoken' ) {
131 throw new LocalizedHttpException( $this->makeMessageValue( $msg ), 403 );
132 }
133
134 if ( $code === 'missingtitle' ) {
135 throw new LocalizedHttpException( $this->makeMessageValue( $msg ), 404 );
136 }
137
138 if ( $code === 'articleexists' ) {
139 throw new LocalizedHttpException( $this->makeMessageValue( $msg ), 409 );
140 }
141
142 if ( $code === 'editconflict' ) {
143 throw new LocalizedHttpException( $this->makeMessageValue( $msg ), 409 );
144 }
145
146 if ( $code === 'ratelimited' ) {
147 throw new LocalizedHttpException( $this->makeMessageValue( $msg ), 429 );
148 }
149
150 // Fall through to generic handling of the error (status 400).
151 parent::throwHttpExceptionForActionModuleError( $msg, $statusCode );
152 }
153
166 protected function getActionModuleToken() {
167 $body = $this->getValidatedBody();
168
169 if ( $this->getSession()->getProvider()->safeAgainstCsrf() ) {
170 if ( !empty( $body['token'] ) ) {
171 throw new LocalizedHttpException(
172 new MessageValue( 'rest-extraneous-csrf-token' ),
173 400
174 );
175 }
176
177 // Since the session is safe against CSRF, just use a known-good token.
178 return $this->getUser()->getEditToken();
179 } else {
180 return $body['token'] ?? '';
181 }
182 }
183
184 protected function mapActionModuleResponse(
185 WebResponse $actionModuleResponse,
186 array $actionModuleResult,
187 Response $response
188 ) {
189 parent::mapActionModuleResponse(
190 $actionModuleResponse,
191 $actionModuleResult,
192 $response
193 );
194
195 if ( $actionModuleResult['edit']['new'] ?? false ) {
196 $response->setStatus( 201 );
197 }
198 }
199
200}
Base class for REST handlers that are implemented by mapping to an existing ApiModule.
makeMessageValue(IApiMessage $msg)
Constructs a MessageValue from an IApiMessage.
Base class for REST API handlers that perform page edits (main slot only).
__construct(Config $config, IContentHandlerFactory $contentHandlerFactory, TitleParser $titleParser, TitleFormatter $titleFormatter, RevisionLookup $revisionLookup)
needsWriteAccess()
Indicates whether this route requires write access.
getActionModuleToken()
Determines the CSRF token to be passed to the action module.
getTitleParameter()
Returns the requested title.
mapActionModuleResult(array $data)
Maps an action API result to a REST API result.mixed Data structure to be converted to JSON and wrapp...
throwHttpExceptionForActionModuleError(IApiMessage $msg, $statusCode=400)
Throws a HttpException for a given IApiMessage that represents an error.Never returns normally....
IContentHandlerFactory $contentHandlerFactory
mapActionModuleResponse(WebResponse $actionModuleResponse, array $actionModuleResult, Response $response)
Transfers relevant information, such as header values, from the WebResponse constructed by the action...
getValidatedBody()
Fetch the validated body.
Definition Handler.php:269
This is the base exception class for non-fatal exceptions thrown from REST handlers.
setStatus( $code, $reasonPhrase='')
Set the status code and, optionally, reason phrase.
Definition Response.php:44
Value object representing a content slot associated with a page revision.
Allow programs to request this object from WebRequest::response() and handle all outputting (or lack ...
Value object representing a message for i18n.
Interface for configuration instances.
Definition Config.php:30
Interface for messages with machine-readable data for use by the API.
getApiCode()
Returns a machine-readable code for use by the API.
Service for looking up page revisions.
A title formatter service for MediaWiki.
A title parser service for MediaWiki.
$content
Definition router.php:76