MediaWiki  master
ApiFormatBase.php
Go to the documentation of this file.
1 <?php
24 
30 abstract class ApiFormatBase extends ApiBase {
31  private $mIsHtml, $mFormat;
32  private $mBuffer, $mDisabled = false;
33  private $mIsWrappedHtml = false;
34  private $mHttpStatus = false;
35  protected $mForceDefaultParams = false;
36 
42  public function __construct( ApiMain $main, $format ) {
43  parent::__construct( $main, $format );
44 
45  $this->mIsHtml = ( substr( $format, -2, 2 ) === 'fm' ); // ends with 'fm'
46  if ( $this->mIsHtml ) {
47  $this->mFormat = substr( $format, 0, -2 ); // remove ending 'fm'
48  $this->mIsWrappedHtml = $this->getMain()->getCheck( 'wrappedhtml' );
49  } else {
50  $this->mFormat = $format;
51  }
52  $this->mFormat = strtoupper( $this->mFormat );
53  }
54 
63  abstract public function getMimeType();
64 
72  public function getFilename() {
73  if ( $this->getIsWrappedHtml() ) {
74  return 'api-result-wrapped.json';
75  } elseif ( $this->getIsHtml() ) {
76  return 'api-result.html';
77  } else {
78  $exts = MediaWikiServices::getInstance()->getMimeAnalyzer()
79  ->getExtensionsForType( $this->getMimeType() );
80  $ext = $exts ? strtok( $exts, ' ' ) : strtolower( $this->mFormat );
81  return "api-result.$ext";
82  }
83  }
84 
89  public function getFormat() {
90  return $this->mFormat;
91  }
92 
99  public function getIsHtml() {
100  return $this->mIsHtml;
101  }
102 
108  protected function getIsWrappedHtml() {
109  return $this->mIsWrappedHtml;
110  }
111 
117  public function disable() {
118  $this->mDisabled = true;
119  }
120 
125  public function isDisabled() {
126  return $this->mDisabled;
127  }
128 
137  public function canPrintErrors() {
138  return true;
139  }
140 
147  public function forceDefaultParams() {
148  $this->mForceDefaultParams = true;
149  }
150 
156  protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
157  if ( !$this->mForceDefaultParams ) {
158  return parent::getParameterFromSettings( $paramName, $paramSettings, $parseLimit );
159  }
160 
161  if ( !is_array( $paramSettings ) ) {
162  return $paramSettings;
163  }
164 
165  return $paramSettings[self::PARAM_DFLT] ?? null;
166  }
167 
173  public function setHttpStatus( $code ) {
174  if ( $this->mDisabled ) {
175  return;
176  }
177 
178  if ( $this->getIsHtml() ) {
179  $this->mHttpStatus = $code;
180  } else {
181  $this->getMain()->getRequest()->response()->statusHeader( $code );
182  }
183  }
184 
189  public function initPrinter( $unused = false ) {
190  if ( $this->mDisabled ) {
191  return;
192  }
193 
194  $mime = $this->getIsWrappedHtml()
195  ? 'text/mediawiki-api-prettyprint-wrapped'
196  : ( $this->getIsHtml() ? 'text/html' : $this->getMimeType() );
197 
198  // Some printers (ex. Feed) do their own header settings,
199  // in which case $mime will be set to null
200  if ( $mime === null ) {
201  return; // skip any initialization
202  }
203 
204  $this->getMain()->getRequest()->response()->header( "Content-Type: $mime; charset=utf-8" );
205 
206  // Set X-Frame-Options API results (T41180)
207  $apiFrameOptions = $this->getConfig()->get( 'ApiFrameOptions' );
208  if ( $apiFrameOptions ) {
209  $this->getMain()->getRequest()->response()->header( "X-Frame-Options: $apiFrameOptions" );
210  }
211 
212  // Set a Content-Disposition header so something downloading an API
213  // response uses a halfway-sensible filename (T128209).
214  $header = 'Content-Disposition: inline';
215  $filename = $this->getFilename();
216  $compatFilename = mb_convert_encoding( $filename, 'ISO-8859-1' );
217  if ( preg_match( '/^[0-9a-zA-Z!#$%&\'*+\-.^_`|~]+$/', $compatFilename ) ) {
218  $header .= '; filename=' . $compatFilename;
219  } else {
220  $header .= '; filename="'
221  . preg_replace( '/([\0-\x1f"\x5c\x7f])/', '\\\\$1', $compatFilename ) . '"';
222  }
223  if ( $compatFilename !== $filename ) {
224  $value = "UTF-8''" . rawurlencode( $filename );
225  // rawurlencode() encodes more characters than RFC 5987 specifies. Unescape the ones it allows.
226  $value = strtr( $value, [
227  '%21' => '!', '%23' => '#', '%24' => '$', '%26' => '&', '%2B' => '+', '%5E' => '^',
228  '%60' => '`', '%7C' => '|',
229  ] );
230  $header .= '; filename*=' . $value;
231  }
232  $this->getMain()->getRequest()->response()->header( $header );
233  }
234 
238  public function closePrinter() {
239  if ( $this->mDisabled ) {
240  return;
241  }
242 
243  $mime = $this->getMimeType();
244  if ( $this->getIsHtml() && $mime !== null ) {
245  $format = $this->getFormat();
246  $lcformat = strtolower( $format );
247  $result = $this->getBuffer();
248 
249  $context = new DerivativeContext( $this->getMain() );
250  $skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
251  $context->setSkin( $skinFactory->makeSkin( 'apioutput' ) );
252  $context->setTitle( SpecialPage::getTitleFor( 'ApiHelp' ) );
253  $out = new OutputPage( $context );
254  $context->setOutput( $out );
255 
256  $out->setRobotPolicy( 'noindex,nofollow' );
257  $out->addModuleStyles( 'mediawiki.apipretty' );
258  $out->setPageTitle( $context->msg( 'api-format-title' ) );
259 
260  if ( !$this->getIsWrappedHtml() ) {
261  // When the format without suffix 'fm' is defined, there is a non-html version
262  if ( $this->getMain()->getModuleManager()->isDefined( $lcformat, 'format' ) ) {
263  if ( !$this->getRequest()->wasPosted() ) {
264  $nonHtmlUrl = strtok( $this->getRequest()->getFullRequestURL(), '?' )
265  . '?' . $this->getRequest()->appendQueryValue( 'format', $lcformat );
266  $msg = $context->msg( 'api-format-prettyprint-header-hyperlinked' )
267  ->params( $format, $lcformat, $nonHtmlUrl );
268  } else {
269  $msg = $context->msg( 'api-format-prettyprint-header' )->params( $format, $lcformat );
270  }
271  } else {
272  $msg = $context->msg( 'api-format-prettyprint-header-only-html' )->params( $format );
273  }
274 
275  $header = $msg->parseAsBlock();
276  $out->addHTML(
277  Html::rawElement( 'div', [ 'class' => 'api-pretty-header' ],
279  )
280  );
281 
282  if ( $this->mHttpStatus && $this->mHttpStatus !== 200 ) {
283  $out->addHTML(
284  Html::rawElement( 'div', [ 'class' => 'api-pretty-header api-pretty-status' ],
285  $this->msg(
286  'api-format-prettyprint-status',
287  $this->mHttpStatus,
288  HttpStatus::getMessage( $this->mHttpStatus )
289  )->parse()
290  )
291  );
292  }
293  }
294 
295  if ( Hooks::run( 'ApiFormatHighlight', [ $context, $result, $mime, $format ] ) ) {
296  $out->addHTML(
297  Html::element( 'pre', [ 'class' => 'api-pretty-content' ], $result )
298  );
299  }
300 
301  if ( $this->getIsWrappedHtml() ) {
302  // This is a special output mode mainly intended for ApiSandbox use
303  $time = $this->getMain()->getRequest()->getElapsedTime();
304  $json = FormatJson::encode(
305  [
306  'status' => (int)( $this->mHttpStatus ?: 200 ),
307  'statustext' => HttpStatus::getMessage( $this->mHttpStatus ?: 200 ),
308  'html' => $out->getHTML(),
309  'modules' => array_values( array_unique( array_merge(
310  $out->getModules(),
311  $out->getModuleStyles()
312  ) ) ),
313  'continue' => $this->getResult()->getResultData( 'continue' ),
314  'time' => round( $time * 1000 ),
315  ],
316  false, FormatJson::ALL_OK
317  );
318 
319  // T68776: OutputHandler::mangleFlashPolicy() avoids a nasty bug in
320  // Flash, but what it does isn't friendly for the API, so we need to
321  // work around it.
322  if ( preg_match( '/<\s*cross-domain-policy\s*>/i', $json ) ) {
323  $json = preg_replace(
324  '/<(\s*cross-domain-policy\s*)>/i', '\\u003C$1\\u003E', $json
325  );
326  }
327 
328  echo $json;
329  } else {
330  // API handles its own clickjacking protection.
331  // Note, that $wgBreakFrames will still override $wgApiFrameOptions for format mode.
332  $out->allowClickjacking();
333  $out->output();
334  }
335  } else {
336  // For non-HTML output, clear all errors that might have been
337  // displayed if display_errors=On
338  ob_clean();
339 
340  echo $this->getBuffer();
341  }
342  }
343 
348  public function printText( $text ) {
349  $this->mBuffer .= $text;
350  }
351 
356  public function getBuffer() {
357  return $this->mBuffer;
358  }
359 
360  public function getAllowedParams() {
361  $ret = [];
362  if ( $this->getIsHtml() ) {
363  $ret['wrappedhtml'] = [
364  ApiBase::PARAM_DFLT => false,
365  ApiBase::PARAM_HELP_MSG => 'apihelp-format-param-wrappedhtml',
366 
367  ];
368  }
369  return $ret;
370  }
371 
372  protected function getExamplesMessages() {
373  return [
374  'action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName()
375  => [ 'apihelp-format-example-generic', $this->getFormat() ]
376  ];
377  }
378 
379  public function getHelpUrls() {
380  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Data_formats';
381  }
382 
383 }
384 
isDisabled()
Whether the printer is disabled.
disable()
Disable the formatter.
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:231
getResult()
Get the result object.
Definition: ApiBase.php:640
getIsWrappedHtml()
Returns true when the special wrapped mode is enabled.
__construct(ApiMain $main, $format)
If $format ends with &#39;fm&#39;, pretty-print the output in HTML.
initPrinter( $unused=false)
Initialize the printer function and prepare the output headers.
const PARAM_DFLT
(null|boolean|integer|string) Default value of the parameter.
Definition: ApiBase.php:55
getMain()
Get the main module.
Definition: ApiBase.php:536
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
An IContextSource implementation which will inherit context from another source but allow individual ...
const ALL_OK
Skip escaping as many characters as reasonably possible.
Definition: FormatJson.php:55
closePrinter()
Finish printing and output buffered data.
printText( $text)
Append text to the output buffer.
static encode( $value, $pretty=false, $escaping=0)
Returns the JSON representation of a value.
Definition: FormatJson.php:115
setHttpStatus( $code)
Set the HTTP status code to be used for the response.
This is the abstract base class for API formatters.
getFilename()
Return a filename for this module&#39;s output.
IContextSource $context
static fixHelpLinks( $html, $helptitle=null, $localModules=[])
Replace Special:ApiHelp links with links to api.php.
Definition: ApiHelp.php:189
getMimeType()
Overriding class returns the MIME type that should be sent to the client.
getIsHtml()
Returns true when the HTML pretty-printer should be used.
static getMessage( $code)
Get the message associated with an HTTP response status code.
Definition: HttpStatus.php:34
msg( $key,... $params)
This is the method for getting translated interface messages.
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:42
canPrintErrors()
Whether this formatter can handle printing API errors.
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:520
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don&#39;t need a full Title object...
Definition: SpecialPage.php:83
$header
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter...
Definition: ApiBase.php:131
getBuffer()
Get the contents of the buffer.
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
if(!is_readable( $file)) $ext
Definition: router.php:48
This abstract class implements many basic API functions, and is the base of all API classes...
Definition: ApiBase.php:42
forceDefaultParams()
Ignore request parameters, force a default.
getParameterFromSettings( $paramName, $paramSettings, $parseLimit)
Overridden to honor $this->forceDefaultParams(), if applicable .
getFormat()
Get the internal format name.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200