MediaWiki  master
ApiFormatBase.php
Go to the documentation of this file.
1 <?php
26 
32 abstract class ApiFormatBase extends ApiBase {
33  private $mIsHtml, $mFormat;
34  private $mBuffer, $mDisabled = false;
35  private $mIsWrappedHtml = false;
36  private $mHttpStatus = false;
37  protected $mForceDefaultParams = false;
38 
44  public function __construct( ApiMain $main, $format ) {
45  parent::__construct( $main, $format );
46 
47  $this->mIsHtml = ( substr( $format, -2, 2 ) === 'fm' ); // ends with 'fm'
48  if ( $this->mIsHtml ) {
49  $this->mFormat = substr( $format, 0, -2 ); // remove ending 'fm'
50  $this->mIsWrappedHtml = $this->getMain()->getCheck( 'wrappedhtml' );
51  } else {
52  $this->mFormat = $format;
53  }
54  $this->mFormat = strtoupper( $this->mFormat );
55  }
56 
65  abstract public function getMimeType();
66 
74  public function getFilename() {
75  if ( $this->getIsWrappedHtml() ) {
76  return 'api-result-wrapped.json';
77  } elseif ( $this->getIsHtml() ) {
78  return 'api-result.html';
79  } else {
80  $mimeAnalyzer = MediaWikiServices::getInstance()->getMimeAnalyzer();
81  $ext = $mimeAnalyzer->getExtensionFromMimeTypeOrNull( $this->getMimeType() )
82  ?? strtolower( $this->mFormat );
83  return "api-result.$ext";
84  }
85  }
86 
91  public function getFormat() {
92  return $this->mFormat;
93  }
94 
101  public function getIsHtml() {
102  return $this->mIsHtml;
103  }
104 
110  protected function getIsWrappedHtml() {
111  return $this->mIsWrappedHtml;
112  }
113 
119  public function disable() {
120  $this->mDisabled = true;
121  }
122 
127  public function isDisabled() {
128  return $this->mDisabled;
129  }
130 
139  public function canPrintErrors() {
140  return true;
141  }
142 
149  public function forceDefaultParams() {
150  $this->mForceDefaultParams = true;
151  }
152 
158  protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
159  if ( !$this->mForceDefaultParams ) {
160  return parent::getParameterFromSettings( $paramName, $paramSettings, $parseLimit );
161  }
162 
163  if ( !is_array( $paramSettings ) ) {
164  return $paramSettings;
165  }
166 
167  return $paramSettings[ParamValidator::PARAM_DEFAULT] ?? null;
168  }
169 
175  public function setHttpStatus( $code ) {
176  if ( $this->mDisabled ) {
177  return;
178  }
179 
180  if ( $this->getIsHtml() ) {
181  $this->mHttpStatus = $code;
182  } else {
183  $this->getMain()->getRequest()->response()->statusHeader( $code );
184  }
185  }
186 
191  public function initPrinter( $unused = false ) {
192  if ( $this->mDisabled ) {
193  return;
194  }
195 
196  $mime = $this->getIsWrappedHtml()
197  ? 'text/mediawiki-api-prettyprint-wrapped'
198  : ( $this->getIsHtml() ? 'text/html' : $this->getMimeType() );
199 
200  // Some printers (ex. Feed) do their own header settings,
201  // in which case $mime will be set to null
202  if ( $mime === null ) {
203  return; // skip any initialization
204  }
205 
206  $this->getMain()->getRequest()->response()->header( "Content-Type: $mime; charset=utf-8" );
207 
208  // Set X-Frame-Options API results (T41180)
209  $apiFrameOptions = $this->getConfig()->get( MainConfigNames::ApiFrameOptions );
210  if ( $apiFrameOptions ) {
211  $this->getMain()->getRequest()->response()->header( "X-Frame-Options: $apiFrameOptions" );
212  }
213 
214  // Set a Content-Disposition header so something downloading an API
215  // response uses a halfway-sensible filename (T128209).
216  $header = 'Content-Disposition: inline';
217  $filename = $this->getFilename();
218  $compatFilename = mb_convert_encoding( $filename, 'ISO-8859-1' );
219  if ( preg_match( '/^[0-9a-zA-Z!#$%&\'*+\-.^_`|~]+$/', $compatFilename ) ) {
220  $header .= '; filename=' . $compatFilename;
221  } else {
222  $header .= '; filename="'
223  . preg_replace( '/([\0-\x1f"\x5c\x7f])/', '\\\\$1', $compatFilename ) . '"';
224  }
225  if ( $compatFilename !== $filename ) {
226  $value = "UTF-8''" . rawurlencode( $filename );
227  // rawurlencode() encodes more characters than RFC 5987 specifies. Unescape the ones it allows.
228  $value = strtr( $value, [
229  '%21' => '!', '%23' => '#', '%24' => '$', '%26' => '&', '%2B' => '+', '%5E' => '^',
230  '%60' => '`', '%7C' => '|',
231  ] );
232  $header .= '; filename*=' . $value;
233  }
234  $this->getMain()->getRequest()->response()->header( $header );
235  }
236 
240  public function closePrinter() {
241  if ( $this->mDisabled ) {
242  return;
243  }
244 
245  $mime = $this->getMimeType();
246  if ( $this->getIsHtml() && $mime !== null ) {
247  $format = $this->getFormat();
248  $lcformat = strtolower( $format );
249  $result = $this->getBuffer();
250 
251  $context = new DerivativeContext( $this->getMain() );
252  $skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
253  $context->setSkin( $skinFactory->makeSkin( 'apioutput' ) );
254  $context->setTitle( SpecialPage::getTitleFor( 'ApiHelp' ) );
255  $out = new OutputPage( $context );
256  $context->setOutput( $out );
257 
258  $out->setRobotPolicy( 'noindex,nofollow' );
259  $out->addModuleStyles( 'mediawiki.apipretty' );
260  $out->setPageTitle( $context->msg( 'api-format-title' ) );
261 
262  if ( !$this->getIsWrappedHtml() ) {
263  // When the format without suffix 'fm' is defined, there is a non-html version
264  if ( $this->getMain()->getModuleManager()->isDefined( $lcformat, 'format' ) ) {
265  if ( !$this->getRequest()->wasPosted() ) {
266  $nonHtmlUrl = strtok( $this->getRequest()->getFullRequestURL(), '?' )
267  . '?' . $this->getRequest()->appendQueryValue( 'format', $lcformat );
268  $msg = $context->msg( 'api-format-prettyprint-header-hyperlinked' )
269  ->params( $format, $lcformat, $nonHtmlUrl );
270  } else {
271  $msg = $context->msg( 'api-format-prettyprint-header' )->params( $format, $lcformat );
272  }
273  } else {
274  $msg = $context->msg( 'api-format-prettyprint-header-only-html' )->params( $format );
275  }
276 
277  $header = $msg->parseAsBlock();
278  $out->addHTML(
279  Html::rawElement( 'div', [ 'class' => 'api-pretty-header' ],
281  )
282  );
283 
284  if ( $this->mHttpStatus && $this->mHttpStatus !== 200 ) {
285  $out->addHTML(
286  Html::rawElement( 'div', [ 'class' => [ 'api-pretty-header', 'api-pretty-status' ] ],
287  $this->msg(
288  'api-format-prettyprint-status',
289  $this->mHttpStatus,
290  HttpStatus::getMessage( $this->mHttpStatus )
291  )->parse()
292  )
293  );
294  }
295  }
296 
297  if ( $this->getHookRunner()->onApiFormatHighlight( $context, $result, $mime, $format ) ) {
298  $out->addHTML(
299  Html::element( 'pre', [ 'class' => 'api-pretty-content' ], $result )
300  );
301  }
302 
303  if ( $this->getIsWrappedHtml() ) {
304  // This is a special output mode mainly intended for ApiSandbox use
305  $time = $this->getMain()->getRequest()->getElapsedTime();
306  echo FormatJson::encode(
307  [
308  'status' => (int)( $this->mHttpStatus ?: 200 ),
309  'statustext' => HttpStatus::getMessage( $this->mHttpStatus ?: 200 ),
310  'html' => $out->getHTML(),
311  'modules' => array_values( array_unique( array_merge(
312  $out->getModules(),
313  $out->getModuleStyles()
314  ) ) ),
315  'continue' => $this->getResult()->getResultData( 'continue' ),
316  'time' => round( $time * 1000 ),
317  ],
318  false, FormatJson::ALL_OK
319  );
320  } else {
321  // API handles its own clickjacking protection.
322  // Note, that $wgBreakFrames will still override $wgApiFrameOptions for format mode.
323  $out->setPreventClickjacking( false );
324  $out->output();
325  }
326  } else {
327  // For non-HTML output, clear all errors that might have been
328  // displayed if display_errors=On
329  ob_clean();
330 
331  echo $this->getBuffer();
332  }
333  }
334 
339  public function printText( $text ) {
340  $this->mBuffer .= $text;
341  }
342 
347  public function getBuffer() {
348  return $this->mBuffer;
349  }
350 
351  public function getAllowedParams() {
352  $ret = [];
353  if ( $this->getIsHtml() ) {
354  $ret['wrappedhtml'] = [
355  ParamValidator::PARAM_DEFAULT => false,
356  ApiBase::PARAM_HELP_MSG => 'apihelp-format-param-wrappedhtml',
357  ];
358  }
359  return $ret;
360  }
361 
362  protected function getExamplesMessages() {
363  return [
364  'action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName()
365  => [ 'apihelp-format-example-generic', $this->getFormat() ]
366  ];
367  }
368 
369  public function getHelpUrls() {
370  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Data_formats';
371  }
372 
373 }
374 
This abstract class implements many basic API functions, and is the base of all API classes.
Definition: ApiBase.php:56
getModuleManager()
Get the module manager, or null if this module has no sub-modules.
Definition: ApiBase.php:307
getMain()
Get the main module.
Definition: ApiBase.php:514
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition: ApiBase.php:163
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:498
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition: ApiBase.php:711
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 determine the value...
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:208
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:52
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:96
const ALL_OK
Skip escaping as many characters as reasonably possible.
Definition: FormatJson.php:55
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:236
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:214
static getMessage( $code)
Get the message associated with an HTTP response status code.
Definition: HttpStatus.php:34
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:54
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.
$mime
Definition: router.php:60
if(!is_readable( $file)) $ext
Definition: router.php:48
$header