MediaWiki master
ApiFormatBase.php
Go to the documentation of this file.
1<?php
31
37abstract class ApiFormatBase extends ApiBase {
38 private bool $mIsHtml;
39 private string $mFormat;
40 private string $mBuffer = '';
41 private bool $mDisabled = false;
42 private $mIsWrappedHtml = false;
43 private $mHttpStatus = false;
44 protected $mForceDefaultParams = false;
45
52 public function __construct( ApiMain $main, $format ) {
53 parent::__construct( $main, $format );
54
55 $this->mIsHtml = str_ends_with( $format, 'fm' );
56 if ( $this->mIsHtml ) {
57 $this->mFormat = substr( $format, 0, -2 ); // remove ending 'fm'
58 $this->mIsWrappedHtml = $this->getMain()->getCheck( 'wrappedhtml' );
59 } else {
60 $this->mFormat = $format;
61 }
62 $this->mFormat = strtoupper( $this->mFormat );
63 }
64
73 abstract public function getMimeType();
74
83 public function getFilename() {
84 if ( $this->getIsWrappedHtml() ) {
85 return 'api-result-wrapped.json';
86 }
87
88 if ( $this->getIsHtml() ) {
89 return 'api-result.html';
90 }
91
92 $mimeAnalyzer = MediaWikiServices::getInstance()->getMimeAnalyzer();
93 $ext = $mimeAnalyzer->getExtensionFromMimeTypeOrNull( $this->getMimeType() )
94 ?? strtolower( $this->mFormat );
95 return "api-result.$ext";
96 }
97
103 public function getFormat() {
104 return $this->mFormat;
105 }
106
113 public function getIsHtml() {
114 return $this->mIsHtml;
115 }
116
123 protected function getIsWrappedHtml() {
124 return $this->mIsWrappedHtml;
125 }
126
132 public function disable() {
133 $this->mDisabled = true;
134 }
135
141 public function isDisabled() {
142 return $this->mDisabled;
143 }
144
152 public function canPrintErrors() {
153 return true;
154 }
155
163 public function forceDefaultParams() {
164 $this->mForceDefaultParams = true;
165 }
166
172 protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
173 if ( !$this->mForceDefaultParams ) {
174 return parent::getParameterFromSettings( $paramName, $paramSettings, $parseLimit );
175 }
176
177 if ( !is_array( $paramSettings ) ) {
178 return $paramSettings;
179 }
180
181 return $paramSettings[ParamValidator::PARAM_DEFAULT] ?? null;
182 }
183
189 public function setHttpStatus( $code ) {
190 if ( $this->mDisabled ) {
191 return;
192 }
193
194 if ( $this->getIsHtml() ) {
195 $this->mHttpStatus = $code;
196 } else {
197 $this->getMain()->getRequest()->response()->statusHeader( $code );
198 }
199 }
200
205 public function initPrinter( $unused = false ) {
206 if ( $this->mDisabled ) {
207 return;
208 }
209
210 if ( $this->getIsHtml() && $this->getMain()->getCacheMode() === 'public' ) {
211 // The HTML may contain user secrets! T354045
212 $this->getMain()->setCacheMode( 'anon-public-user-private' );
213 }
214
215 $mime = $this->getIsWrappedHtml()
216 ? 'text/mediawiki-api-prettyprint-wrapped'
217 : ( $this->getIsHtml() ? 'text/html' : $this->getMimeType() );
218
219 // Some printers (ex. Feed) do their own header settings,
220 // in which case $mime will be set to null
221 if ( $mime === null ) {
222 return; // skip any initialization
223 }
224
225 $this->getMain()->getRequest()->response()->header( "Content-Type: $mime; charset=utf-8" );
226
227 // Set X-Frame-Options API results (T41180)
228 $apiFrameOptions = $this->getConfig()->get( MainConfigNames::ApiFrameOptions );
229 if ( $apiFrameOptions ) {
230 $this->getMain()->getRequest()->response()->header( "X-Frame-Options: $apiFrameOptions" );
231 }
232
233 // Set a Content-Disposition header so something downloading an API
234 // response uses a halfway-sensible filename (T128209).
235 $header = 'Content-Disposition: inline';
236 $filename = $this->getFilename();
237 $compatFilename = mb_convert_encoding( $filename, 'ISO-8859-1' );
238 if ( preg_match( '/^[0-9a-zA-Z!#$%&\'*+\-.^_`|~]+$/', $compatFilename ) ) {
239 $header .= '; filename=' . $compatFilename;
240 } else {
241 $header .= '; filename="'
242 . preg_replace( '/([\0-\x1f"\x5c\x7f])/', '\\\\$1', $compatFilename ) . '"';
243 }
244 if ( $compatFilename !== $filename ) {
245 $value = "UTF-8''" . rawurlencode( $filename );
246 // rawurlencode() encodes more characters than RFC 5987 specifies. Unescape the ones it allows.
247 $value = strtr( $value, [
248 '%21' => '!', '%23' => '#', '%24' => '$', '%26' => '&', '%2B' => '+', '%5E' => '^',
249 '%60' => '`', '%7C' => '|',
250 ] );
251 $header .= '; filename*=' . $value;
252 }
253 $this->getMain()->getRequest()->response()->header( $header );
254 }
255
259 public function closePrinter() {
260 if ( $this->mDisabled ) {
261 return;
262 }
263
264 $mime = $this->getMimeType();
265 if ( $this->getIsHtml() && $mime !== null ) {
266 $format = $this->getFormat();
267 $lcformat = strtolower( $format );
268 $result = $this->getBuffer();
269
270 $context = new DerivativeContext( $this->getMain() );
271 $skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
272 $context->setSkin( $skinFactory->makeSkin( 'apioutput' ) );
273 $context->setTitle( SpecialPage::getTitleFor( 'ApiHelp' ) );
274 $out = new OutputPage( $context );
275 $context->setOutput( $out );
276
277 $out->setRobotPolicy( 'noindex,nofollow' );
278 $out->addModuleStyles( 'mediawiki.apipretty' );
279 $out->setPageTitleMsg( $context->msg( 'api-format-title' ) );
280
281 if ( !$this->getIsWrappedHtml() ) {
282 // When the format without suffix 'fm' is defined, there is a non-html version
283 if ( $this->getMain()->getModuleManager()->isDefined( $lcformat, 'format' ) ) {
284 if ( !$this->getRequest()->wasPosted() ) {
285 $nonHtmlUrl = strtok( $this->getRequest()->getFullRequestURL(), '?' )
286 . '?' . $this->getRequest()->appendQueryValue( 'format', $lcformat );
287 $msg = $context->msg( 'api-format-prettyprint-header-hyperlinked' )
288 ->params( $format, $lcformat, $nonHtmlUrl );
289 } else {
290 $msg = $context->msg( 'api-format-prettyprint-header' )->params( $format, $lcformat );
291 }
292 } else {
293 $msg = $context->msg( 'api-format-prettyprint-header-only-html' )->params( $format );
294 }
295
296 $header = $msg->parseAsBlock();
297 $out->addHTML(
298 Html::rawElement( 'div', [ 'class' => 'api-pretty-header' ],
299 ApiHelp::fixHelpLinks( $header )
300 )
301 );
302
303 if ( $this->mHttpStatus && $this->mHttpStatus !== 200 ) {
304 $out->addHTML(
305 Html::rawElement( 'div', [ 'class' => [ 'api-pretty-header', 'api-pretty-status' ] ],
306 $this->msg(
307 'api-format-prettyprint-status',
308 $this->mHttpStatus,
309 HttpStatus::getMessage( $this->mHttpStatus )
310 )->parse()
311 )
312 );
313 }
314 }
315
316 if ( $this->getHookRunner()->onApiFormatHighlight( $context, $result, $mime, $format ) ) {
317 $out->addHTML(
318 Html::element( 'pre', [ 'class' => 'api-pretty-content' ], $result )
319 );
320 }
321
322 if ( $this->getIsWrappedHtml() ) {
323 // This is a special output mode mainly intended for ApiSandbox use
324 $time = $this->getMain()->getRequest()->getElapsedTime();
325 echo FormatJson::encode(
326 [
327 'status' => (int)( $this->mHttpStatus ?: 200 ),
328 'statustext' => HttpStatus::getMessage( $this->mHttpStatus ?: 200 ),
329 'html' => $out->getHTML(),
330 'modules' => array_values( array_unique( array_merge(
331 $out->getModules(),
332 $out->getModuleStyles()
333 ) ) ),
334 'continue' => $this->getResult()->getResultData( 'continue' ),
335 'time' => round( $time * 1000 ),
336 ],
337 false, FormatJson::ALL_OK
338 );
339 } else {
340 // API handles its own clickjacking protection.
341 // Note: $wgBreakFrames will still override $wgApiFrameOptions for format mode.
342 $out->setPreventClickjacking( false );
343 $out->output();
344 }
345 } else {
346 // For non-HTML output, clear all errors that might have been
347 // displayed if display_errors=On
348 ob_clean();
349
350 echo $this->getBuffer();
351 }
352 }
353
359 public function printText( $text ) {
360 $this->mBuffer .= $text;
361 }
362
368 public function getBuffer() {
369 return $this->mBuffer;
370 }
371
372 public function getAllowedParams() {
373 $ret = [];
374 if ( $this->getIsHtml() ) {
375 $ret['wrappedhtml'] = [
376 ParamValidator::PARAM_DEFAULT => false,
377 ApiBase::PARAM_HELP_MSG => 'apihelp-format-param-wrappedhtml',
378 ];
379 }
380 return $ret;
381 }
382
383 protected function getExamplesMessages() {
384 return [
385 'action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName()
386 => [ 'apihelp-format-example-generic', $this->getFormat() ]
387 ];
388 }
389
390 public function getHelpUrls() {
391 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Data_formats';
392 }
393
394}
395
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:65
getModuleManager()
Get the module manager, or null if this module has no submodules.
Definition ApiBase.php:341
getMain()
Get the main module.
Definition ApiBase.php:560
getResult()
Get the result object.
Definition ApiBase.php:681
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:172
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:542
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:766
This is the abstract base class for API formatters.
getHelpUrls()
Return links to more detailed help pages about the module.
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
__construct(ApiMain $main, $format)
If $format ends with 'fm', pretty-print the output in HTML.
getMimeType()
Overriding class returns the MIME type that should be sent to the client.
getFormat()
Get the internal format name.
getFilename()
Return a filename for this module's output.
printText( $text)
Append text to the output buffer.
initPrinter( $unused=false)
Initialize the printer function and prepare the output headers.
disable()
Disable the formatter.
getBuffer()
Get the contents of the buffer.
getIsWrappedHtml()
Returns true when the special-wrapped mode is enabled.
canPrintErrors()
Whether this formatter can handle printing API errors.
getExamplesMessages()
Returns usage examples for this module.
isDisabled()
Whether the printer is disabled.
forceDefaultParams()
Ignore request parameters, force a default.
getIsHtml()
Returns true when the HTML pretty-printer should be used.
getParameterFromSettings( $paramName, $paramSettings, $parseLimit)
Overridden to honor $this->forceDefaultParams(), if applicable Using the settings,...
setHttpStatus( $code)
Set the HTTP status code to be used for the response.
closePrinter()
Finish printing and output buffered data.
This is the main API class, used for both external and internal processing.
Definition ApiMain.php:67
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.
Service locator for MediaWiki core services.
This is one of the Core classes and should be read at least once by any new developers.
Parent class for all special pages.
Service for formatting and validating API parameters.
$header