X-Git-Url: https://scripts.mit.edu/gitweb/autoinstalls/mediawiki.git/blobdiff_plain/19e297c21b10b1b8a3acad5e73fc71dcb35db44a..6932310fd58ebef145fa01eb76edf7150284d8ea:/includes/api/ApiFormatBase.php diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php index 8b371652..c5f2fcfa 100644 --- a/includes/api/ApiFormatBase.php +++ b/includes/api/ApiFormatBase.php @@ -1,10 +1,10 @@ @gmail.com + * Copyright © 2006 Yuri Astrakhan "@gmail.com" * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,53 +24,64 @@ * @file */ -if ( !defined( 'MEDIAWIKI' ) ) { - // Eclipse helper - will be ignored in production - require_once( 'ApiBase.php' ); -} - /** * This is the abstract base class for API formatters. * * @ingroup API */ abstract class ApiFormatBase extends ApiBase { - - private $mIsHtml, $mFormat, $mUnescapeAmps, $mHelp, $mCleared; - private $mBufferResult = false, $mBuffer, $mDisabled = false; + private $mIsHtml, $mFormat, $mUnescapeAmps, $mHelp; + private $mBuffer, $mDisabled = false; + private $mIsWrappedHtml = false; + private $mHttpStatus = false; + protected $mForceDefaultParams = false; /** - * Constructor * If $format ends with 'fm', pretty-print the output in HTML. - * @param $main ApiMain - * @param $format string Format name + * @param ApiMain $main + * @param string $format Format name */ - public function __construct( $main, $format ) { + public function __construct( ApiMain $main, $format ) { parent::__construct( $main, $format ); - $this->mIsHtml = ( substr( $format, - 2, 2 ) === 'fm' ); // ends with 'fm' + $this->mIsHtml = ( substr( $format, -2, 2 ) === 'fm' ); // ends with 'fm' if ( $this->mIsHtml ) { - $this->mFormat = substr( $format, 0, - 2 ); // remove ending 'fm' + $this->mFormat = substr( $format, 0, -2 ); // remove ending 'fm' + $this->mIsWrappedHtml = $this->getMain()->getCheck( 'wrappedhtml' ); } else { $this->mFormat = $format; } $this->mFormat = strtoupper( $this->mFormat ); - $this->mCleared = false; } /** - * Overriding class returns the mime type that should be sent to the client. - * This method is not called if getIsHtml() returns true. + * Overriding class returns the MIME type that should be sent to the client. + * + * When getIsHtml() returns true, the return value here is used for syntax + * highlighting but the client sees text/html. + * * @return string */ - public abstract function getMimeType(); + abstract public function getMimeType(); /** - * Whether this formatter needs raw data such as _element tags - * @return bool + * Return a filename for this module's output. + * @note If $this->getIsWrappedHtml() || $this->getIsHtml(), you'll very + * likely want to fall back to this class's version. + * @since 1.27 + * @return string Generally this should be "api-result.$ext", and must be + * encoded for inclusion in a Content-Disposition header's filename parameter. */ - public function getNeedsRawData() { - return false; + public function getFilename() { + if ( $this->getIsWrappedHtml() ) { + return 'api-result-wrapped.json'; + } elseif ( $this->getIsHtml() ) { + return 'api-result.html'; + } else { + $exts = MimeMagic::singleton()->getExtensionsForType( $this->getMimeType() ); + $ext = $exts ? strtok( $exts, ' ' ) : strtolower( $this->mFormat ); + return "api-result.$ext"; + } } /** @@ -81,19 +92,6 @@ abstract class ApiFormatBase extends ApiBase { return $this->mFormat; } - /** - * Specify whether or not sequences like " should be unescaped - * to " . This should only be set to true for the help message - * when rendered in the default (xmlfm) format. This is a temporary - * special-case fix that should be removed once the help has been - * reworked to use a fully HTML interface. - * - * @param $b bool Whether or not ampersands should be escaped. - */ - public function setUnescapeAmps ( $b ) { - $this->mUnescapeAmps = $b; - } - /** * Returns true when the HTML pretty-printer should be used. * The default implementation assumes that formats ending with 'fm' @@ -105,269 +103,274 @@ abstract class ApiFormatBase extends ApiBase { } /** - * Whether this formatter can format the help message in a nice way. - * By default, this returns the same as getIsHtml(). - * When action=help is set explicitly, the help will always be shown + * Returns true when the special wrapped mode is enabled. + * @since 1.27 * @return bool */ - public function getWantsHelp() { - return $this->getIsHtml(); + protected function getIsWrappedHtml() { + return $this->mIsWrappedHtml; } /** - * Disable the formatter completely. This causes calls to initPrinter(), - * printText() and closePrinter() to be ignored. + * Disable the formatter. + * + * This causes calls to initPrinter() and closePrinter() to be ignored. */ public function disable() { $this->mDisabled = true; } + /** + * Whether the printer is disabled + * @return bool + */ public function isDisabled() { return $this->mDisabled; } /** - * Initialize the printer function and prepare the output headers, etc. - * This method must be the first outputing method during execution. - * A help screen's header is printed for the HTML-based output - * @param $isError bool Whether an error message is printed + * Whether this formatter can handle printing API errors. + * + * If this returns false, then on API errors the default printer will be + * instantiated. + * @since 1.23 + * @return bool */ - function initPrinter( $isError ) { - if ( $this->mDisabled ) { - return; - } - $isHtml = $this->getIsHtml(); - $mime = $isHtml ? 'text/html' : $this->getMimeType(); - $script = wfScript( 'api' ); - - // Some printers (ex. Feed) do their own header settings, - // in which case $mime will be set to null - if ( is_null( $mime ) ) { - return; // skip any initialization - } - - header( "Content-Type: $mime; charset=utf-8" ); - - if ( $isHtml ) { -?> - - - -mUnescapeAmps ) { -?> MediaWiki API - MediaWiki API Result - - - - -
- -You are looking at the HTML representation of the mFormat ); ?> format.
-HTML is good for debugging, but probably is not suitable for your application.
-See complete documentation, or -API help for more information. -
-mForceDefaultParams = true; + } + /** + * Overridden to honor $this->forceDefaultParams(), if applicable + * @inheritDoc + * @since 1.26 + */ + protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) { + if ( !$this->mForceDefaultParams ) { + return parent::getParameterFromSettings( $paramName, $paramSettings, $parseLimit ); + } - } -?> -
-mDisabled ) {
+			return;
+		}
 
+		if ( $this->getIsHtml() ) {
+			$this->mHttpStatus = $code;
+		} else {
+			$this->getMain()->getRequest()->response()->statusHeader( $code );
 		}
 	}
 
 	/**
-	 * Finish printing. Closes HTML tags.
+	 * Initialize the printer function and prepare the output headers.
+	 * @param bool $unused Always false since 1.25
 	 */
-	public function closePrinter() {
+	public function initPrinter( $unused = false ) {
 		if ( $this->mDisabled ) {
 			return;
 		}
-		if ( $this->getIsHtml() ) {
-?>
 
-
- - -getIsWrappedHtml() + ? 'text/mediawiki-api-prettyprint-wrapped' + : ( $this->getIsHtml() ? 'text/html' : $this->getMimeType() ); + // Some printers (ex. Feed) do their own header settings, + // in which case $mime will be set to null + if ( $mime === null ) { + return; // skip any initialization + } + $this->getMain()->getRequest()->response()->header( "Content-Type: $mime; charset=utf-8" ); + + // Set X-Frame-Options API results (T41180) + $apiFrameOptions = $this->getConfig()->get( 'ApiFrameOptions' ); + if ( $apiFrameOptions ) { + $this->getMain()->getRequest()->response()->header( "X-Frame-Options: $apiFrameOptions" ); } + + // Set a Content-Disposition header so something downloading an API + // response uses a halfway-sensible filename (T128209). + $filename = $this->getFilename(); + $this->getMain()->getRequest()->response()->header( + "Content-Disposition: inline; filename=\"{$filename}\"" + ); } /** - * The main format printing function. Call it to output the result - * string to the user. This function will automatically output HTML - * when format name ends in 'fm'. - * @param $text string + * Finish printing and output buffered data. */ - public function printText( $text ) { + public function closePrinter() { if ( $this->mDisabled ) { return; } - if ( $this->mBufferResult ) { - $this->mBuffer = $text; - } elseif ( $this->getIsHtml() ) { - echo $this->formatHTML( $text ); + + $mime = $this->getMimeType(); + if ( $this->getIsHtml() && $mime !== null ) { + $format = $this->getFormat(); + $lcformat = strtolower( $format ); + $result = $this->getBuffer(); + + $context = new DerivativeContext( $this->getMain() ); + $context->setSkin( SkinFactory::getDefaultInstance()->makeSkin( 'apioutput' ) ); + $context->setTitle( SpecialPage::getTitleFor( 'ApiHelp' ) ); + $out = new OutputPage( $context ); + $context->setOutput( $out ); + + $out->addModuleStyles( 'mediawiki.apipretty' ); + $out->setPageTitle( $context->msg( 'api-format-title' ) ); + + if ( !$this->getIsWrappedHtml() ) { + // When the format without suffix 'fm' is defined, there is a non-html version + if ( $this->getMain()->getModuleManager()->isDefined( $lcformat, 'format' ) ) { + if ( !$this->getRequest()->wasPosted() ) { + $nonHtmlUrl = strtok( $this->getRequest()->getFullRequestURL(), '?' ) + . '?' . $this->getRequest()->appendQueryValue( 'format', $lcformat ); + $msg = $context->msg( 'api-format-prettyprint-header-hyperlinked' ) + ->params( $format, $lcformat, $nonHtmlUrl ); + } else { + $msg = $context->msg( 'api-format-prettyprint-header' )->params( $format, $lcformat ); + } + } else { + $msg = $context->msg( 'api-format-prettyprint-header-only-html' )->params( $format ); + } + + $header = $msg->parseAsBlock(); + $out->addHTML( + Html::rawElement( 'div', [ 'class' => 'api-pretty-header' ], + ApiHelp::fixHelpLinks( $header ) + ) + ); + + if ( $this->mHttpStatus && $this->mHttpStatus !== 200 ) { + $out->addHTML( + Html::rawElement( 'div', [ 'class' => 'api-pretty-header api-pretty-status' ], + $this->msg( + 'api-format-prettyprint-status', + $this->mHttpStatus, + HttpStatus::getMessage( $this->mHttpStatus ) + )->parse() + ) + ); + } + } + + if ( Hooks::run( 'ApiFormatHighlight', [ $context, $result, $mime, $format ] ) ) { + $out->addHTML( + Html::element( 'pre', [ 'class' => 'api-pretty-content' ], $result ) + ); + } + + if ( $this->getIsWrappedHtml() ) { + // This is a special output mode mainly intended for ApiSandbox use + $time = microtime( true ) - $this->getConfig()->get( 'RequestTime' ); + $json = FormatJson::encode( + [ + 'status' => (int)( $this->mHttpStatus ?: 200 ), + 'statustext' => HttpStatus::getMessage( $this->mHttpStatus ?: 200 ), + 'html' => $out->getHTML(), + 'modules' => array_values( array_unique( array_merge( + $out->getModules(), + $out->getModuleScripts(), + $out->getModuleStyles() + ) ) ), + 'continue' => $this->getResult()->getResultData( 'continue' ), + 'time' => round( $time * 1000 ), + ], + false, FormatJson::ALL_OK + ); + + // T68776: wfMangleFlashPolicy() is needed to avoid a nasty bug in + // Flash, but what it does isn't friendly for the API, so we need to + // work around it. + if ( preg_match( '/\<\s*cross-domain-policy\s*\>/i', $json ) ) { + $json = preg_replace( + '/\<(\s*cross-domain-policy\s*)\>/i', '\\u003C$1\\u003E', $json + ); + } + + echo $json; + } else { + // API handles its own clickjacking protection. + // Note, that $wgBreakFrames will still override $wgApiFrameOptions for format mode. + $out->allowClickjacking(); + $out->output(); + } } else { // For non-HTML output, clear all errors that might have been // displayed if display_errors=On - // Do this only once, of course - if ( !$this->mCleared ) { - ob_clean(); - $this->mCleared = true; - } - echo $text; + ob_clean(); + + echo $this->getBuffer(); } } /** - * Get the contents of the buffer. + * Append text to the output buffer. + * @param string $text */ - public function getBuffer() { - return $this->mBuffer; - } - /** - * Set the flag to buffer the result instead of printing it. - */ - public function setBufferResult( $value ) { - $this->mBufferResult = $value; + public function printText( $text ) { + $this->mBuffer .= $text; } /** - * Sets whether the pretty-printer should format *bold* and $italics$ - * @param $help bool + * Get the contents of the buffer. + * @return string */ - public function setHelp( $help = true ) { - $this->mHelp = $help; + public function getBuffer() { + return $this->mBuffer; } - /** - * Pretty-print various elements in HTML format, such as xml tags and - * URLs. This method also escapes characters like < - * @param $text string - * @return string - */ - protected function formatHTML( $text ) { - global $wgUrlProtocols; - - // Escape everything first for full coverage - $text = htmlspecialchars( $text ); - - // encode all comments or tags as safe blue strings - $text = preg_replace( '/\<(!--.*?--|.*?)\>/', '<\1>', $text ); - // identify URLs - $protos = implode( "|", $wgUrlProtocols ); - // This regex hacks around bug 13218 (" included in the URL) - $text = preg_replace( "#(($protos).*?)(")?([ \\'\"<>\n]|<|>|")#", '\\1\\3\\4', $text ); - // identify requests to api.php - $text = preg_replace( "#api\\.php\\?[^ \\()<\n\t]+#", '\\0', $text ); - if ( $this->mHelp ) { - // make strings inside * bold - $text = preg_replace( "#\\*[^<>\n]+\\*#", '\\0', $text ); - // make strings inside $ italic - $text = preg_replace( "#\\$[^<>\n]+\\$#", '\\0', $text ); - } + public function getAllowedParams() { + $ret = []; + if ( $this->getIsHtml() ) { + $ret['wrappedhtml'] = [ + ApiBase::PARAM_DFLT => false, + ApiBase::PARAM_HELP_MSG => 'apihelp-format-param-wrappedhtml', - /** - * Temporary fix for bad links in help messages. As a special case, - * XML-escaped metachars are de-escaped one level in the help message - * for legibility. Should be removed once we have completed a fully-HTML - * version of the help message. - */ - if ( $this->mUnescapeAmps ) { - $text = preg_replace( '/&(amp|quot|lt|gt);/', '&\1;', $text ); + ]; } - - return $text; + return $ret; } - protected function getExamples() { - return 'api.php?action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName(); + protected function getExamplesMessages() { + return [ + 'action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName() + => [ 'apihelp-format-example-generic', $this->getFormat() ] + ]; } - public function getDescription() { - return $this->getIsHtml() ? ' (pretty-print in HTML)' : ''; + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Data_formats'; } - public static function getBaseVersion() { - return __CLASS__ . ': $Id$'; - } } /** - * This printer is used to wrap an instance of the Feed class - * @ingroup API + * For really cool vim folding this needs to be at the end: + * vim: foldmarker=@{,@} foldmethod=marker */ -class ApiFormatFeedWrapper extends ApiFormatBase { - - public function __construct( $main ) { - parent::__construct( $main, 'feed' ); - } - - /** - * Call this method to initialize output data. See execute() - * @param $result ApiResult - * @param $feed object an instance of one of the $wgFeedClasses classes - * @param $feedItems array of FeedItem objects - */ - public static function setResult( $result, $feed, $feedItems ) { - // Store output in the Result data. - // This way we can check during execution if any error has occured - // Disable size checking for this because we can't continue - // cleanly; size checking would cause more problems than it'd - // solve - $result->disableSizeCheck(); - $result->addValue( null, '_feed', $feed ); - $result->addValue( null, '_feeditems', $feedItems ); - $result->enableSizeCheck(); - } - - /** - * Feed does its own headers - */ - public function getMimeType() { - return null; - } - - /** - * Optimization - no need to sanitize data that will not be needed - */ - public function getNeedsRawData() { - return true; - } - - /** - * This class expects the result data to be in a custom format set by self::setResult() - * $result['_feed'] - an instance of one of the $wgFeedClasses classes - * $result['_feeditems'] - an array of FeedItem instances - */ - public function execute() { - $data = $this->getResultData(); - if ( isset( $data['_feed'] ) && isset( $data['_feeditems'] ) ) { - $feed = $data['_feed']; - $items = $data['_feeditems']; - - $feed->outHeader(); - foreach ( $items as & $item ) { - $feed->outItem( $item ); - } - $feed->outFooter(); - } else { - // Error has occured, print something useful - ApiBase::dieDebug( __METHOD__, 'Invalid feed class/item' ); - } - } - - public function getVersion() { - return __CLASS__ . ': $Id$'; - } -} \ No newline at end of file