MediaWiki master
ApiFormatBase.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Api;
10
21
27abstract class ApiFormatBase extends ApiBase {
28 private bool $mIsHtml;
29 private string $mFormat;
30 private string $mBuffer = '';
31 private bool $mDisabled = false;
33 private $mIsWrappedHtml = false;
35 private $mHttpStatus = false;
37 protected $mForceDefaultParams = false;
38
45 public function __construct( ApiMain $main, string $format ) {
46 parent::__construct( $main, $format );
47
48 $this->mIsHtml = str_ends_with( $format, 'fm' );
49 if ( $this->mIsHtml ) {
50 $this->mFormat = substr( $format, 0, -2 ); // remove ending 'fm'
51 $this->mIsWrappedHtml = $this->getMain()->getCheck( 'wrappedhtml' );
52 } else {
53 $this->mFormat = $format;
54 }
55 $this->mFormat = strtoupper( $this->mFormat );
56 }
57
66 abstract public function getMimeType();
67
76 public function getFilename() {
77 if ( $this->getIsWrappedHtml() ) {
78 return 'api-result-wrapped.json';
79 }
80
81 if ( $this->getIsHtml() ) {
82 return 'api-result.html';
83 }
84
85 $mimeAnalyzer = MediaWikiServices::getInstance()->getMimeAnalyzer();
86 $ext = $mimeAnalyzer->getExtensionFromMimeTypeOrNull( $this->getMimeType() )
87 ?? strtolower( $this->mFormat );
88 return "api-result.$ext";
89 }
90
96 public function getFormat() {
97 return $this->mFormat;
98 }
99
106 public function getIsHtml() {
107 return $this->mIsHtml;
108 }
109
116 protected function getIsWrappedHtml() {
117 return $this->mIsWrappedHtml;
118 }
119
125 public function disable() {
126 $this->mDisabled = true;
127 }
128
134 public function isDisabled() {
135 return $this->mDisabled;
136 }
137
145 public function canPrintErrors() {
146 return true;
147 }
148
156 public function forceDefaultParams() {
157 $this->mForceDefaultParams = true;
158 }
159
165 protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
166 if ( !$this->mForceDefaultParams ) {
167 return parent::getParameterFromSettings( $paramName, $paramSettings, $parseLimit );
168 }
169
170 if ( !is_array( $paramSettings ) ) {
171 return $paramSettings;
172 }
173
174 return $paramSettings[ParamValidator::PARAM_DEFAULT] ?? null;
175 }
176
182 public function setHttpStatus( $code ) {
183 if ( $this->mDisabled ) {
184 return;
185 }
186
187 if ( $this->getIsHtml() ) {
188 $this->mHttpStatus = $code;
189 } else {
190 $this->getMain()->getRequest()->response()->statusHeader( $code );
191 }
192 }
193
198 public function initPrinter( $unused = false ) {
199 if ( $this->mDisabled ) {
200 return;
201 }
202
203 if ( $this->getIsHtml() && $this->getMain()->getCacheMode() === 'public' ) {
204 // The HTML may contain user secrets! T354045
205 $this->getMain()->setCacheMode( 'anon-public-user-private' );
206 }
207
208 $mime = $this->getIsWrappedHtml()
209 ? 'text/mediawiki-api-prettyprint-wrapped'
210 : ( $this->getIsHtml() ? 'text/html' : $this->getMimeType() );
211
212 // Some printers (ex. Feed) do their own header settings,
213 // in which case $mime will be set to null
214 if ( $mime === null ) {
215 return; // skip any initialization
216 }
217
218 if ( $mime !== 'text/html' ) {
219 ContentSecurityPolicy::sendRestrictiveHeader();
220 }
221 $this->getMain()->getRequest()->response()->header( "Content-Type: $mime; charset=utf-8" );
222
223 // Set X-Frame-Options API results (T41180)
224 $apiFrameOptions = $this->getConfig()->get( MainConfigNames::ApiFrameOptions );
225 if ( $apiFrameOptions ) {
226 $this->getMain()->getRequest()->response()->header( "X-Frame-Options: $apiFrameOptions" );
227 }
228
229 // Set a Content-Disposition header so something downloading an API
230 // response uses a halfway-sensible filename (T128209).
231 $header = 'Content-Disposition: inline';
232 $filename = $this->getFilename();
233 $compatFilename = mb_convert_encoding( $filename, 'ISO-8859-1' );
234 if ( preg_match( '/^[0-9a-zA-Z!#$%&\'*+\-.^_`|~]+$/', $compatFilename ) ) {
235 $header .= '; filename=' . $compatFilename;
236 } else {
237 $header .= '; filename="'
238 . preg_replace( '/([\0-\x1f"\x5c\x7f])/', '\\\\$1', $compatFilename ) . '"';
239 }
240 if ( $compatFilename !== $filename ) {
241 $value = "UTF-8''" . rawurlencode( $filename );
242 // rawurlencode() encodes more characters than RFC 5987 specifies. Unescape the ones it allows.
243 $value = strtr( $value, [
244 '%21' => '!', '%23' => '#', '%24' => '$', '%26' => '&', '%2B' => '+', '%5E' => '^',
245 '%60' => '`', '%7C' => '|',
246 ] );
247 $header .= '; filename*=' . $value;
248 }
249 $this->getMain()->getRequest()->response()->header( $header );
250 }
251
255 public function closePrinter() {
256 if ( $this->mDisabled ) {
257 return;
258 }
259
260 $mime = $this->getMimeType();
261 if ( $this->getIsHtml() && $mime !== null ) {
262 $format = $this->getFormat();
263 $lcformat = strtolower( $format );
264 $result = $this->getBuffer();
265
266 $context = new DerivativeContext( $this->getMain() );
267 $skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
268 $context->setSkin( $skinFactory->makeSkin( 'apioutput' ) );
269 $context->setTitle( SpecialPage::getTitleFor( 'ApiHelp' ) );
270 $out = new OutputPage( $context );
271 $context->setOutput( $out );
272
273 $out->setRobotPolicy( 'noindex,nofollow' );
274 $out->addModuleStyles( 'mediawiki.apipretty' );
275 $out->setPageTitleMsg( $context->msg( 'api-format-title' ) );
276
277 if ( !$this->getIsWrappedHtml() ) {
278 // When the format without suffix 'fm' is defined, there is a non-html version
279 if ( $this->getMain()->getModuleManager()->isDefined( $lcformat, 'format' ) ) {
280 if ( !$this->getRequest()->wasPosted() ) {
281 $nonHtmlUrl = strtok( $this->getRequest()->getFullRequestURL(), '?' )
282 . '?' . $this->getRequest()->appendQueryValue( 'format', $lcformat );
283 $msg = $context->msg( 'api-format-prettyprint-header-hyperlinked' )
284 ->params( $format, $lcformat, $nonHtmlUrl );
285 } else {
286 $msg = $context->msg( 'api-format-prettyprint-header' )->params( $format, $lcformat );
287 }
288 } else {
289 $msg = $context->msg( 'api-format-prettyprint-header-only-html' )->params( $format );
290 }
291
292 $header = $msg->parseAsBlock();
293 $out->addHTML(
294 Html::rawElement( 'div', [ 'class' => 'api-pretty-header' ],
295 ApiHelp::fixHelpLinks( $header )
296 )
297 );
298
299 if ( $this->mHttpStatus && $this->mHttpStatus !== 200 ) {
300 $out->addHTML(
301 Html::rawElement( 'div', [ 'class' => [ 'api-pretty-header', 'api-pretty-status' ] ],
302 $this->msg(
303 'api-format-prettyprint-status',
304 $this->mHttpStatus,
305 HttpStatus::getMessage( $this->mHttpStatus )
306 )->parse()
307 )
308 );
309 }
310 }
311
312 if ( $this->getHookRunner()->onApiFormatHighlight( $context, $result, $mime, $format ) ) {
313 $out->addHTML(
314 Html::element( 'pre', [ 'class' => 'api-pretty-content' ], $result )
315 );
316 }
317
318 if ( $this->getIsWrappedHtml() ) {
319 // This is a special output mode mainly intended for ApiSandbox use
320 $time = $this->getMain()->getRequest()->getElapsedTime();
321 echo FormatJson::encode(
322 [
323 'status' => (int)( $this->mHttpStatus ?: 200 ),
324 'statustext' => HttpStatus::getMessage( $this->mHttpStatus ?: 200 ),
325 'html' => $out->getHTML(),
326 'modules' => array_values( array_unique( array_merge(
327 $out->getModules(),
328 $out->getModuleStyles()
329 ) ) ),
330 'continue' => $this->getResult()->getResultData( 'continue' ),
331 'time' => round( $time * 1000 ),
332 ],
333 false, FormatJson::ALL_OK
334 );
335 } else {
336 // API handles its own clickjacking protection.
337 // Note: $wgBreakFrames will still override $wgApiFrameOptions for format mode.
338 $out->getMetadata()->setPreventClickjacking( false );
339 $out->output();
340 }
341 } else {
342 // For non-HTML output, clear all errors that might have been
343 // displayed if display_errors=On
344 ob_clean();
345
346 echo $this->getBuffer();
347 }
348 }
349
355 public function printText( $text ) {
356 $this->mBuffer .= $text;
357 }
358
364 public function getBuffer() {
365 return $this->mBuffer;
366 }
367
369 public function getAllowedParams() {
370 $ret = [];
371 if ( $this->getIsHtml() ) {
372 $ret['wrappedhtml'] = [
373 ParamValidator::PARAM_DEFAULT => false,
374 ApiBase::PARAM_HELP_MSG => 'apihelp-format-param-wrappedhtml',
375 ];
376 }
377 return $ret;
378 }
379
381 protected function getExamplesMessages() {
382 return [
383 'action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName()
384 => [ 'apihelp-format-example-generic', $this->getFormat() ]
385 ];
386 }
387
389 public function getHelpUrls() {
390 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Data_formats';
391 }
392
393}
394
401class_alias( ApiFormatBase::class, 'ApiFormatBase' );
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:61
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:543
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:767
getMain()
Get the main module.
Definition ApiBase.php:561
getModuleManager()
Get the module manager, or null if this module has no submodules.
Definition ApiBase.php:326
getResult()
Get the result object.
Definition ApiBase.php:682
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:167
This is the abstract base class for API formatters.
getFilename()
Return a filename for this module's output.
getIsWrappedHtml()
Returns true when the special-wrapped mode is enabled.
printText( $text)
Append text to the output buffer.
getParameterFromSettings( $paramName, $paramSettings, $parseLimit)
Overridden to honor $this->forceDefaultParams(), if applicable Using the settings,...
isDisabled()
Whether the printer is disabled.
disable()
Disable the formatter.
getBuffer()
Get the contents of the buffer.
getFormat()
Get the internal format name.
getHelpUrls()
Return links to more detailed help pages about the module.1.25, returning boolean false is deprecated...
getExamplesMessages()
Returns usage examples for this module.Return value has query strings as keys, with values being eith...
getIsHtml()
Returns true when the HTML pretty-printer should be used.
closePrinter()
Finish printing and output buffered data.
canPrintErrors()
Whether this formatter can handle printing API errors.
getMimeType()
Overriding class returns the MIME type that should be sent to the client.
forceDefaultParams()
Ignore request parameters, force a default.
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
initPrinter( $unused=false)
Initialize the printer function and prepare the output headers.
setHttpStatus( $code)
Set the HTTP status code to be used for the response.
__construct(ApiMain $main, string $format)
If $format ends with 'fm', pretty-print the output in HTML.
static fixHelpLinks( $html, $helptitle=null, $localModules=[])
Replace Special:ApiHelp links with links to api.php.
Definition ApiHelp.php:201
This is the main API class, used for both external and internal processing.
Definition ApiMain.php:65
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
An IContextSource implementation which will inherit context from another source but allow individual ...
This class is a collection of static functions that serve two purposes:
Definition Html.php:43
JSON formatter wrapper class.
A class containing constants representing the names of configuration variables.
const ApiFrameOptions
Name constant for the ApiFrameOptions setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
This is one of the Core classes and should be read at least once by any new developers.
Handle sending Content-Security-Policy headers.
Parent class for all special pages.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Service for formatting and validating API parameters.
element(SerializerNode $parent, SerializerNode $node, $contents)