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