MediaWiki  master
ApiFormatBase.php
Go to the documentation of this file.
1 <?php
29 
35 abstract 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  $mime = $this->getIsWrappedHtml()
207  ? 'text/mediawiki-api-prettyprint-wrapped'
208  : ( $this->getIsHtml() ? 'text/html' : $this->getMimeType() );
209 
210  // Some printers (ex. Feed) do their own header settings,
211  // in which case $mime will be set to null
212  if ( $mime === null ) {
213  return; // skip any initialization
214  }
215 
216  $this->getMain()->getRequest()->response()->header( "Content-Type: $mime; charset=utf-8" );
217 
218  // Set X-Frame-Options API results (T41180)
219  $apiFrameOptions = $this->getConfig()->get( MainConfigNames::ApiFrameOptions );
220  if ( $apiFrameOptions ) {
221  $this->getMain()->getRequest()->response()->header( "X-Frame-Options: $apiFrameOptions" );
222  }
223 
224  // Set a Content-Disposition header so something downloading an API
225  // response uses a halfway-sensible filename (T128209).
226  $header = 'Content-Disposition: inline';
227  $filename = $this->getFilename();
228  $compatFilename = mb_convert_encoding( $filename, 'ISO-8859-1' );
229  if ( preg_match( '/^[0-9a-zA-Z!#$%&\'*+\-.^_`|~]+$/', $compatFilename ) ) {
230  $header .= '; filename=' . $compatFilename;
231  } else {
232  $header .= '; filename="'
233  . preg_replace( '/([\0-\x1f"\x5c\x7f])/', '\\\\$1', $compatFilename ) . '"';
234  }
235  if ( $compatFilename !== $filename ) {
236  $value = "UTF-8''" . rawurlencode( $filename );
237  // rawurlencode() encodes more characters than RFC 5987 specifies. Unescape the ones it allows.
238  $value = strtr( $value, [
239  '%21' => '!', '%23' => '#', '%24' => '$', '%26' => '&', '%2B' => '+', '%5E' => '^',
240  '%60' => '`', '%7C' => '|',
241  ] );
242  $header .= '; filename*=' . $value;
243  }
244  $this->getMain()->getRequest()->response()->header( $header );
245  }
246 
250  public function closePrinter() {
251  if ( $this->mDisabled ) {
252  return;
253  }
254 
255  $mime = $this->getMimeType();
256  if ( $this->getIsHtml() && $mime !== null ) {
257  $format = $this->getFormat();
258  $lcformat = strtolower( $format );
259  $result = $this->getBuffer();
260 
261  $context = new DerivativeContext( $this->getMain() );
262  $skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
263  $context->setSkin( $skinFactory->makeSkin( 'apioutput' ) );
264  $context->setTitle( SpecialPage::getTitleFor( 'ApiHelp' ) );
265  $out = new OutputPage( $context );
266  $context->setOutput( $out );
267 
268  $out->setRobotPolicy( 'noindex,nofollow' );
269  $out->addModuleStyles( 'mediawiki.apipretty' );
270  $out->setPageTitleMsg( $context->msg( 'api-format-title' ) );
271 
272  if ( !$this->getIsWrappedHtml() ) {
273  // When the format without suffix 'fm' is defined, there is a non-html version
274  if ( $this->getMain()->getModuleManager()->isDefined( $lcformat, 'format' ) ) {
275  if ( !$this->getRequest()->wasPosted() ) {
276  $nonHtmlUrl = strtok( $this->getRequest()->getFullRequestURL(), '?' )
277  . '?' . $this->getRequest()->appendQueryValue( 'format', $lcformat );
278  $msg = $context->msg( 'api-format-prettyprint-header-hyperlinked' )
279  ->params( $format, $lcformat, $nonHtmlUrl );
280  } else {
281  $msg = $context->msg( 'api-format-prettyprint-header' )->params( $format, $lcformat );
282  }
283  } else {
284  $msg = $context->msg( 'api-format-prettyprint-header-only-html' )->params( $format );
285  }
286 
287  $header = $msg->parseAsBlock();
288  $out->addHTML(
289  Html::rawElement( 'div', [ 'class' => 'api-pretty-header' ],
291  )
292  );
293 
294  if ( $this->mHttpStatus && $this->mHttpStatus !== 200 ) {
295  $out->addHTML(
296  Html::rawElement( 'div', [ 'class' => [ 'api-pretty-header', 'api-pretty-status' ] ],
297  $this->msg(
298  'api-format-prettyprint-status',
299  $this->mHttpStatus,
300  HttpStatus::getMessage( $this->mHttpStatus )
301  )->parse()
302  )
303  );
304  }
305  }
306 
307  if ( $this->getHookRunner()->onApiFormatHighlight( $context, $result, $mime, $format ) ) {
308  $out->addHTML(
309  Html::element( 'pre', [ 'class' => 'api-pretty-content' ], $result )
310  );
311  }
312 
313  if ( $this->getIsWrappedHtml() ) {
314  // This is a special output mode mainly intended for ApiSandbox use
315  $time = $this->getMain()->getRequest()->getElapsedTime();
316  echo FormatJson::encode(
317  [
318  'status' => (int)( $this->mHttpStatus ?: 200 ),
319  'statustext' => HttpStatus::getMessage( $this->mHttpStatus ?: 200 ),
320  'html' => $out->getHTML(),
321  'modules' => array_values( array_unique( array_merge(
322  $out->getModules(),
323  $out->getModuleStyles()
324  ) ) ),
325  'continue' => $this->getResult()->getResultData( 'continue' ),
326  'time' => round( $time * 1000 ),
327  ],
328  false, FormatJson::ALL_OK
329  );
330  } else {
331  // API handles its own clickjacking protection.
332  // Note: $wgBreakFrames will still override $wgApiFrameOptions for format mode.
333  $out->setPreventClickjacking( false );
334  $out->output();
335  }
336  } else {
337  // For non-HTML output, clear all errors that might have been
338  // displayed if display_errors=On
339  ob_clean();
340 
341  echo $this->getBuffer();
342  }
343  }
344 
350  public function printText( $text ) {
351  $this->mBuffer .= $text;
352  }
353 
359  public function getBuffer() {
360  return $this->mBuffer;
361  }
362 
363  public function getAllowedParams() {
364  $ret = [];
365  if ( $this->getIsHtml() ) {
366  $ret['wrappedhtml'] = [
367  ParamValidator::PARAM_DEFAULT => false,
368  ApiBase::PARAM_HELP_MSG => 'apihelp-format-param-wrappedhtml',
369  ];
370  }
371  return $ret;
372  }
373 
374  protected function getExamplesMessages() {
375  return [
376  'action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName()
377  => [ 'apihelp-format-example-generic', $this->getFormat() ]
378  ];
379  }
380 
381  public function getHelpUrls() {
382  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Data_formats';
383  }
384 
385 }
386 
This abstract class implements many basic API functions, and is the base of all API classes.
Definition: ApiBase.php:62
getModuleManager()
Get the module manager, or null if this module has no submodules.
Definition: ApiBase.php:327
getMain()
Get the main module.
Definition: ApiBase.php:546
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition: ApiBase.php:169
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:528
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition: ApiBase.php:752
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.
static fixHelpLinks( $html, $helptitle=null, $localModules=[])
Replace Special:ApiHelp links with links to api.php.
Definition: ApiHelp.php:215
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:64
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 ...
static encode( $value, $pretty=false, $escaping=0)
Returns the JSON representation of a value.
Definition: FormatJson.php:98
const ALL_OK
Skip escaping as many characters as reasonably possible.
Definition: FormatJson.php:57
static getMessage( $code)
Get the message associated with an HTTP response status code.
Definition: HttpStatus.php:34
This class is a collection of static functions that serve two purposes:
Definition: Html.php:57
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.
Definition: OutputPage.php:93
Parent class for all special pages.
Definition: SpecialPage.php:65
Service for formatting and validating API parameters.
$mime
Definition: router.php:60
if(!is_readable( $file)) $ext
Definition: router.php:48
$header