MediaWiki master
ApiFormatBase.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Api;
24
25use HttpStatus;
34
40abstract class ApiFormatBase extends ApiBase {
41 private bool $mIsHtml;
42 private string $mFormat;
43 private string $mBuffer = '';
44 private bool $mDisabled = false;
46 private $mIsWrappedHtml = false;
48 private $mHttpStatus = false;
50 protected $mForceDefaultParams = false;
51
58 public function __construct( ApiMain $main, string $format ) {
59 parent::__construct( $main, $format );
60
61 $this->mIsHtml = str_ends_with( $format, 'fm' );
62 if ( $this->mIsHtml ) {
63 $this->mFormat = substr( $format, 0, -2 ); // remove ending 'fm'
64 $this->mIsWrappedHtml = $this->getMain()->getCheck( 'wrappedhtml' );
65 } else {
66 $this->mFormat = $format;
67 }
68 $this->mFormat = strtoupper( $this->mFormat );
69 }
70
79 abstract public function getMimeType();
80
89 public function getFilename() {
90 if ( $this->getIsWrappedHtml() ) {
91 return 'api-result-wrapped.json';
92 }
93
94 if ( $this->getIsHtml() ) {
95 return 'api-result.html';
96 }
97
98 $mimeAnalyzer = MediaWikiServices::getInstance()->getMimeAnalyzer();
99 $ext = $mimeAnalyzer->getExtensionFromMimeTypeOrNull( $this->getMimeType() )
100 ?? strtolower( $this->mFormat );
101 return "api-result.$ext";
102 }
103
109 public function getFormat() {
110 return $this->mFormat;
111 }
112
119 public function getIsHtml() {
120 return $this->mIsHtml;
121 }
122
129 protected function getIsWrappedHtml() {
130 return $this->mIsWrappedHtml;
131 }
132
138 public function disable() {
139 $this->mDisabled = true;
140 }
141
147 public function isDisabled() {
148 return $this->mDisabled;
149 }
150
158 public function canPrintErrors() {
159 return true;
160 }
161
169 public function forceDefaultParams() {
170 $this->mForceDefaultParams = true;
171 }
172
178 protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
179 if ( !$this->mForceDefaultParams ) {
180 return parent::getParameterFromSettings( $paramName, $paramSettings, $parseLimit );
181 }
182
183 if ( !is_array( $paramSettings ) ) {
184 return $paramSettings;
185 }
186
187 return $paramSettings[ParamValidator::PARAM_DEFAULT] ?? null;
188 }
189
195 public function setHttpStatus( $code ) {
196 if ( $this->mDisabled ) {
197 return;
198 }
199
200 if ( $this->getIsHtml() ) {
201 $this->mHttpStatus = $code;
202 } else {
203 $this->getMain()->getRequest()->response()->statusHeader( $code );
204 }
205 }
206
211 public function initPrinter( $unused = false ) {
212 if ( $this->mDisabled ) {
213 return;
214 }
215
216 if ( $this->getIsHtml() && $this->getMain()->getCacheMode() === 'public' ) {
217 // The HTML may contain user secrets! T354045
218 $this->getMain()->setCacheMode( 'anon-public-user-private' );
219 }
220
221 $mime = $this->getIsWrappedHtml()
222 ? 'text/mediawiki-api-prettyprint-wrapped'
223 : ( $this->getIsHtml() ? 'text/html' : $this->getMimeType() );
224
225 // Some printers (ex. Feed) do their own header settings,
226 // in which case $mime will be set to null
227 if ( $mime === null ) {
228 return; // skip any initialization
229 }
230
231 $this->getMain()->getRequest()->response()->header( "Content-Type: $mime; charset=utf-8" );
232
233 // Set X-Frame-Options API results (T41180)
234 $apiFrameOptions = $this->getConfig()->get( MainConfigNames::ApiFrameOptions );
235 if ( $apiFrameOptions ) {
236 $this->getMain()->getRequest()->response()->header( "X-Frame-Options: $apiFrameOptions" );
237 }
238
239 // Set a Content-Disposition header so something downloading an API
240 // response uses a halfway-sensible filename (T128209).
241 $header = 'Content-Disposition: inline';
242 $filename = $this->getFilename();
243 $compatFilename = mb_convert_encoding( $filename, 'ISO-8859-1' );
244 if ( preg_match( '/^[0-9a-zA-Z!#$%&\'*+\-.^_`|~]+$/', $compatFilename ) ) {
245 $header .= '; filename=' . $compatFilename;
246 } else {
247 $header .= '; filename="'
248 . preg_replace( '/([\0-\x1f"\x5c\x7f])/', '\\\\$1', $compatFilename ) . '"';
249 }
250 if ( $compatFilename !== $filename ) {
251 $value = "UTF-8''" . rawurlencode( $filename );
252 // rawurlencode() encodes more characters than RFC 5987 specifies. Unescape the ones it allows.
253 $value = strtr( $value, [
254 '%21' => '!', '%23' => '#', '%24' => '$', '%26' => '&', '%2B' => '+', '%5E' => '^',
255 '%60' => '`', '%7C' => '|',
256 ] );
257 $header .= '; filename*=' . $value;
258 }
259 $this->getMain()->getRequest()->response()->header( $header );
260 }
261
265 public function closePrinter() {
266 if ( $this->mDisabled ) {
267 return;
268 }
269
270 $mime = $this->getMimeType();
271 if ( $this->getIsHtml() && $mime !== null ) {
272 $format = $this->getFormat();
273 $lcformat = strtolower( $format );
274 $result = $this->getBuffer();
275
276 $context = new DerivativeContext( $this->getMain() );
277 $skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
278 $context->setSkin( $skinFactory->makeSkin( 'apioutput' ) );
279 $context->setTitle( SpecialPage::getTitleFor( 'ApiHelp' ) );
280 $out = new OutputPage( $context );
281 $context->setOutput( $out );
282
283 $out->setRobotPolicy( 'noindex,nofollow' );
284 $out->addModuleStyles( 'mediawiki.apipretty' );
285 $out->setPageTitleMsg( $context->msg( 'api-format-title' ) );
286
287 if ( !$this->getIsWrappedHtml() ) {
288 // When the format without suffix 'fm' is defined, there is a non-html version
289 if ( $this->getMain()->getModuleManager()->isDefined( $lcformat, 'format' ) ) {
290 if ( !$this->getRequest()->wasPosted() ) {
291 $nonHtmlUrl = strtok( $this->getRequest()->getFullRequestURL(), '?' )
292 . '?' . $this->getRequest()->appendQueryValue( 'format', $lcformat );
293 $msg = $context->msg( 'api-format-prettyprint-header-hyperlinked' )
294 ->params( $format, $lcformat, $nonHtmlUrl );
295 } else {
296 $msg = $context->msg( 'api-format-prettyprint-header' )->params( $format, $lcformat );
297 }
298 } else {
299 $msg = $context->msg( 'api-format-prettyprint-header-only-html' )->params( $format );
300 }
301
302 $header = $msg->parseAsBlock();
303 $out->addHTML(
304 Html::rawElement( 'div', [ 'class' => 'api-pretty-header' ],
306 )
307 );
308
309 if ( $this->mHttpStatus && $this->mHttpStatus !== 200 ) {
310 $out->addHTML(
311 Html::rawElement( 'div', [ 'class' => [ 'api-pretty-header', 'api-pretty-status' ] ],
312 $this->msg(
313 'api-format-prettyprint-status',
314 $this->mHttpStatus,
315 HttpStatus::getMessage( $this->mHttpStatus )
316 )->parse()
317 )
318 );
319 }
320 }
321
322 if ( $this->getHookRunner()->onApiFormatHighlight( $context, $result, $mime, $format ) ) {
323 $out->addHTML(
324 Html::element( 'pre', [ 'class' => 'api-pretty-content' ], $result )
325 );
326 }
327
328 if ( $this->getIsWrappedHtml() ) {
329 // This is a special output mode mainly intended for ApiSandbox use
330 $time = $this->getMain()->getRequest()->getElapsedTime();
331 echo FormatJson::encode(
332 [
333 'status' => (int)( $this->mHttpStatus ?: 200 ),
334 'statustext' => HttpStatus::getMessage( $this->mHttpStatus ?: 200 ),
335 'html' => $out->getHTML(),
336 'modules' => array_values( array_unique( array_merge(
337 $out->getModules(),
338 $out->getModuleStyles()
339 ) ) ),
340 'continue' => $this->getResult()->getResultData( 'continue' ),
341 'time' => round( $time * 1000 ),
342 ],
343 false, FormatJson::ALL_OK
344 );
345 } else {
346 // API handles its own clickjacking protection.
347 // Note: $wgBreakFrames will still override $wgApiFrameOptions for format mode.
348 $out->getMetadata()->setPreventClickjacking( false );
349 $out->output();
350 }
351 } else {
352 // For non-HTML output, clear all errors that might have been
353 // displayed if display_errors=On
354 ob_clean();
355
356 echo $this->getBuffer();
357 }
358 }
359
365 public function printText( $text ) {
366 $this->mBuffer .= $text;
367 }
368
374 public function getBuffer() {
375 return $this->mBuffer;
376 }
377
378 public function getAllowedParams() {
379 $ret = [];
380 if ( $this->getIsHtml() ) {
381 $ret['wrappedhtml'] = [
382 ParamValidator::PARAM_DEFAULT => false,
383 ApiBase::PARAM_HELP_MSG => 'apihelp-format-param-wrappedhtml',
384 ];
385 }
386 return $ret;
387 }
388
389 protected function getExamplesMessages() {
390 return [
391 'action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName()
392 => [ 'apihelp-format-example-generic', $this->getFormat() ]
393 ];
394 }
395
396 public function getHelpUrls() {
397 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Data_formats';
398 }
399
400}
401
408class_alias( ApiFormatBase::class, 'ApiFormatBase' );
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:76
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:571
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:795
getMain()
Get the main module.
Definition ApiBase.php:589
getModuleManager()
Get the module manager, or null if this module has no submodules.
Definition ApiBase.php:354
getResult()
Get the result object.
Definition ApiBase.php:710
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:184
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.
getExamplesMessages()
Returns usage examples for this module.
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:222
This is the main API class, used for both external and internal processing.
Definition ApiMain.php:78
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:56
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.
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)
$header