MediaWiki master
ApiFormatBase.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Api;
10
23
29abstract class ApiFormatBase extends ApiBase {
30 private bool $mIsHtml;
31 private string $mFormat;
32 private string $mBuffer = '';
33 private bool $mDisabled = false;
35 private $mIsWrappedHtml = false;
37 private $mHttpStatus = false;
39 protected $mForceDefaultParams = false;
40
47 public function __construct( ApiMain $main, string $format ) {
48 parent::__construct( $main, $format );
49
50 $this->mIsHtml = str_ends_with( $format, 'fm' );
51 if ( $this->mIsHtml ) {
52 $this->mFormat = substr( $format, 0, -2 ); // remove ending 'fm'
53 $this->mIsWrappedHtml = $this->getMain()->getCheck( 'wrappedhtml' );
54 } else {
55 $this->mFormat = $format;
56 }
57 $this->mFormat = strtoupper( $this->mFormat );
58 }
59
68 abstract public function getMimeType();
69
78 public function getFilename() {
79 if ( $this->getIsWrappedHtml() ) {
80 return 'api-result-wrapped.json';
81 }
82
83 if ( $this->getIsHtml() ) {
84 return 'api-result.html';
85 }
86
87 $mimeAnalyzer = MediaWikiServices::getInstance()->getMimeAnalyzer();
88 $ext = $mimeAnalyzer->getExtensionFromMimeTypeOrNull( $this->getMimeType() )
89 ?? strtolower( $this->mFormat );
90 return "api-result.$ext";
91 }
92
98 public function getFormat() {
99 return $this->mFormat;
100 }
101
108 public function getIsHtml() {
109 return $this->mIsHtml;
110 }
111
118 protected function getIsWrappedHtml() {
119 return $this->mIsWrappedHtml;
120 }
121
127 public function disable() {
128 $this->mDisabled = true;
129 }
130
136 public function isDisabled() {
137 return $this->mDisabled;
138 }
139
147 public function canPrintErrors() {
148 return true;
149 }
150
158 public function forceDefaultParams() {
159 $this->mForceDefaultParams = true;
160 }
161
167 protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
168 if ( !$this->mForceDefaultParams ) {
169 return parent::getParameterFromSettings( $paramName, $paramSettings, $parseLimit );
170 }
171
172 if ( !is_array( $paramSettings ) ) {
173 return $paramSettings;
174 }
175
176 return $paramSettings[ParamValidator::PARAM_DEFAULT] ?? null;
177 }
178
184 public function setHttpStatus( $code ) {
185 if ( $this->mDisabled ) {
186 return;
187 }
188
189 if ( $this->getIsHtml() ) {
190 $this->mHttpStatus = $code;
191 } else {
192 $this->getMain()->getRequest()->response()->statusHeader( $code );
193 }
194 }
195
200 public function initPrinter( $unused = false ) {
201 if ( $this->mDisabled ) {
202 return;
203 }
204
205 if ( $this->getIsHtml() && $this->getMain()->getCacheMode() === 'public' ) {
206 // The HTML may contain user secrets! T354045
207 $this->getMain()->setCacheMode( 'anon-public-user-private' );
208 }
209
210 $mime = $this->getIsWrappedHtml()
211 ? 'text/mediawiki-api-prettyprint-wrapped'
212 : ( $this->getIsHtml() ? 'text/html' : $this->getMimeType() );
213
214 // Some printers (ex. Feed) do their own header settings,
215 // in which case $mime will be set to null
216 if ( $mime === null ) {
217 return; // skip any initialization
218 }
219
220 if ( $mime !== 'text/html' ) {
221 ContentSecurityPolicy::sendRestrictiveHeader();
222 }
223 $this->getMain()->getRequest()->response()->header( "Content-Type: $mime; charset=utf-8" );
224
225 // Set X-Frame-Options API results (T41180)
226 $apiFrameOptions = $this->getConfig()->get( MainConfigNames::ApiFrameOptions );
227 if ( $apiFrameOptions ) {
228 $this->getMain()->getRequest()->response()->header( "X-Frame-Options: $apiFrameOptions" );
229 }
230
231 // Set a Content-Disposition header so something downloading an API
232 // response uses a halfway-sensible filename (T128209).
233 $header = 'Content-Disposition: inline';
234 $filename = $this->getFilename();
235 $compatFilename = mb_convert_encoding( $filename, 'ISO-8859-1' );
236 if ( preg_match( '/^[0-9a-zA-Z!#$%&\'*+\-.^_`|~]+$/', $compatFilename ) ) {
237 $header .= '; filename=' . $compatFilename;
238 } else {
239 $header .= '; filename="'
240 . preg_replace( '/([\0-\x1f"\x5c\x7f])/', '\\\\$1', $compatFilename ) . '"';
241 }
242 if ( $compatFilename !== $filename ) {
243 $value = "UTF-8''" . rawurlencode( $filename );
244 // rawurlencode() encodes more characters than RFC 5987 specifies. Unescape the ones it allows.
245 $value = strtr( $value, [
246 '%21' => '!', '%23' => '#', '%24' => '$', '%26' => '&', '%2B' => '+', '%5E' => '^',
247 '%60' => '`', '%7C' => '|',
248 ] );
249 $header .= '; filename*=' . $value;
250 }
251 $this->getMain()->getRequest()->response()->header( $header );
252 }
253
257 public function closePrinter() {
258 if ( $this->mDisabled ) {
259 return;
260 }
261
262 $mime = $this->getMimeType();
263 if ( $this->getIsHtml() && $mime !== null ) {
264 $format = $this->getFormat();
265 $lcformat = strtolower( $format );
266 $result = $this->getBuffer();
267
268 $context = new DerivativeContext( $this->getMain() );
269 $skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
270 $context->setSkin( $skinFactory->makeSkin( 'apioutput' ) );
271 $context->setTitle( SpecialPage::getTitleFor( 'ApiHelp' ) );
272 $out = new OutputPage( $context );
273 $context->setOutput( $out );
274
275 $out->setRobotPolicy( 'noindex,nofollow' );
276 $out->disallowUserJs();
277 $out->reduceAllowedModules( RL\Module::TYPE_SCRIPTS, RL\Module::ORIGIN_NONE );
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 $format, $lcformat, $nonHtmlUrl );
289 } else {
290 $msg = $context->msg( 'api-format-prettyprint-header', $format, $lcformat );
291 }
292 } else {
293 $msg = $context->msg( 'api-format-prettyprint-header-only-html', $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 $codeHighlighter = MediaWikiServices::getInstance()->getCodeHighlighter();
318 $codeLang = match ( $mime ) {
319 'text/javascript', 'application/json' => 'javascript',
320 'text/xml' => 'xml',
321 // pass unknown mime type as-is
322 default => $mime,
323 };
324 $highlightOutput = $codeHighlighter->highlight( $result, new CodeHighlighterOptions(
325 language: $codeLang,
326 classes: [ 'api-pretty-content' ],
327 ) );
328 $out->addHTML( $highlightOutput->getHtml() );
329 $highlightOutput->getMetadata()->addToOutputPage( $out );
330 }
331
332 if ( $this->getIsWrappedHtml() ) {
333 // This is a special output mode mainly intended for ApiSandbox use
334 $time = $this->getMain()->getRequest()->getElapsedTime();
335 echo FormatJson::encode(
336 [
337 'status' => (int)( $this->mHttpStatus ?: 200 ),
338 'statustext' => HttpStatus::getMessage( $this->mHttpStatus ?: 200 ),
339 'html' => $out->getHTML(),
340 'modules' => array_values( array_unique( array_merge(
341 $out->getModules(),
342 $out->getModuleStyles()
343 ) ) ),
344 'continue' => $this->getResult()->getResultData( 'continue' ),
345 'time' => round( $time * 1000 ),
346 ],
347 false, FormatJson::ALL_OK
348 );
349 } else {
350 // API handles its own clickjacking protection.
351 // Note: $wgBreakFrames will still override $wgApiFrameOptions for format mode.
352 $out->getMetadata()->setPreventClickjacking( false );
353 $out->output();
354 }
355 } else {
356 // For non-HTML output, clear all errors that might have been
357 // displayed if display_errors=On
358 ob_clean();
359
360 echo $this->getBuffer();
361 }
362 }
363
369 public function printText( $text ) {
370 $this->mBuffer .= $text;
371 }
372
378 public function getBuffer() {
379 return $this->mBuffer;
380 }
381
383 public function getAllowedParams() {
384 $ret = [];
385 if ( $this->getIsHtml() ) {
386 $ret['wrappedhtml'] = [
387 ParamValidator::PARAM_DEFAULT => false,
388 ApiBase::PARAM_HELP_MSG => 'apihelp-format-param-wrappedhtml',
389 ];
390 }
391 return $ret;
392 }
393
395 protected function getExamplesMessages() {
396 return [
397 'action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName()
398 => [ 'apihelp-format-example-generic', $this->getFormat() ]
399 ];
400 }
401
403 public function getHelpUrls() {
404 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Data_formats';
405 }
406
407}
408
415class_alias( ApiFormatBase::class, 'ApiFormatBase' );
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:60
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:557
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:781
getMain()
Get the main module.
Definition ApiBase.php:575
getModuleManager()
Get the module manager, or null if this module has no submodules.
Definition ApiBase.php:325
getResult()
Get the result object.
Definition ApiBase.php:696
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:166
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:210
This is the main API class, used for both external and internal processing.
Definition ApiMain.php:66
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:44
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.