MediaWiki master
ApiFormatBase.php
Go to the documentation of this file.
1<?php
29
35abstract class ApiFormatBase extends ApiBase {
36 private $mIsHtml, $mFormat;
37 private $mBuffer, $mDisabled = false;
38 private $mIsWrappedHtml = false;
39 private $mHttpStatus = false;
40 protected $mForceDefaultParams = false;
41
48 public function __construct( ApiMain $main, $format ) {
49 parent::__construct( $main, $format );
50
51 $this->mIsHtml = str_ends_with( $format, 'fm' );
52 if ( $this->mIsHtml ) {
53 $this->mFormat = substr( $format, 0, -2 ); // remove ending 'fm'
54 $this->mIsWrappedHtml = $this->getMain()->getCheck( 'wrappedhtml' );
55 } else {
56 $this->mFormat = $format;
57 }
58 $this->mFormat = strtoupper( $this->mFormat );
59 }
60
69 abstract public function getMimeType();
70
79 public function getFilename() {
80 if ( $this->getIsWrappedHtml() ) {
81 return 'api-result-wrapped.json';
82 }
83
84 if ( $this->getIsHtml() ) {
85 return 'api-result.html';
86 }
87
88 $mimeAnalyzer = MediaWikiServices::getInstance()->getMimeAnalyzer();
89 $ext = $mimeAnalyzer->getExtensionFromMimeTypeOrNull( $this->getMimeType() )
90 ?? strtolower( $this->mFormat );
91 return "api-result.$ext";
92 }
93
99 public function getFormat() {
100 return $this->mFormat;
101 }
102
109 public function getIsHtml() {
110 return $this->mIsHtml;
111 }
112
119 protected function getIsWrappedHtml() {
120 return $this->mIsWrappedHtml;
121 }
122
128 public function disable() {
129 $this->mDisabled = true;
130 }
131
137 public function isDisabled() {
138 return $this->mDisabled;
139 }
140
148 public function canPrintErrors() {
149 return true;
150 }
151
159 public function forceDefaultParams() {
160 $this->mForceDefaultParams = true;
161 }
162
168 protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
169 if ( !$this->mForceDefaultParams ) {
170 return parent::getParameterFromSettings( $paramName, $paramSettings, $parseLimit );
171 }
172
173 if ( !is_array( $paramSettings ) ) {
174 return $paramSettings;
175 }
176
177 return $paramSettings[ParamValidator::PARAM_DEFAULT] ?? null;
178 }
179
185 public function setHttpStatus( $code ) {
186 if ( $this->mDisabled ) {
187 return;
188 }
189
190 if ( $this->getIsHtml() ) {
191 $this->mHttpStatus = $code;
192 } else {
193 $this->getMain()->getRequest()->response()->statusHeader( $code );
194 }
195 }
196
201 public function initPrinter( $unused = false ) {
202 if ( $this->mDisabled ) {
203 return;
204 }
205
206 if ( $this->getIsHtml() && $this->getMain()->getCacheMode() === 'public' ) {
207 // The HTML may contain user secrets! T354045
208 $this->getMain()->setCacheMode( 'anon-public-user-private' );
209 }
210
211 $mime = $this->getIsWrappedHtml()
212 ? 'text/mediawiki-api-prettyprint-wrapped'
213 : ( $this->getIsHtml() ? 'text/html' : $this->getMimeType() );
214
215 // Some printers (ex. Feed) do their own header settings,
216 // in which case $mime will be set to null
217 if ( $mime === null ) {
218 return; // skip any initialization
219 }
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->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
368 public function getAllowedParams() {
369 $ret = [];
370 if ( $this->getIsHtml() ) {
371 $ret['wrappedhtml'] = [
372 ParamValidator::PARAM_DEFAULT => false,
373 ApiBase::PARAM_HELP_MSG => 'apihelp-format-param-wrappedhtml',
374 ];
375 }
376 return $ret;
377 }
378
379 protected function getExamplesMessages() {
380 return [
381 'action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName()
382 => [ 'apihelp-format-example-generic', $this->getFormat() ]
383 ];
384 }
385
386 public function getHelpUrls() {
387 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Data_formats';
388 }
389
390}
391
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:64
getModuleManager()
Get the module manager, or null if this module has no submodules.
Definition ApiBase.php:331
getMain()
Get the main module.
Definition ApiBase.php:550
getResult()
Get the result object.
Definition ApiBase.php:671
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:171
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:532
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:756
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:65
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
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