]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blobdiff - includes/api/ApiFormatBase.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / includes / api / ApiFormatBase.php
index cc7434c6c3a9c8a724fda874220afeda39ee2818..c5f2fcfaa7567fba788f35dae8e3fc47d36acd2d 100644 (file)
@@ -1,11 +1,10 @@
 <?php
-
-/*
- * Created on Sep 19, 2006
+/**
  *
- * API for MediaWiki 1.8+
  *
- * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ * Created on Sep 19, 2006
+ *
+ * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@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
  *
  * You should have received a copy of the GNU General Public License along
  * with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @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 $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) {
-               parent :: __construct($main, $format);
+       public function __construct( ApiMain $main, $format ) {
+               parent::__construct( $main, $format );
 
-               $this->mIsHtml = (substr($format, -2, 2) === 'fm'); // ends with 'fm'
-               if ($this->mIsHtml)
-                       $this->mFormat = substr($format, 0, -2); // remove ending 'fm'
-               else
+               $this->mIsHtml = ( substr( $format, -2, 2 ) === 'fm' ); // ends with 'fm'
+               if ( $this->mIsHtml ) {
+                       $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;
+               }
+               $this->mFormat = strtoupper( $this->mFormat );
        }
 
        /**
-        * 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";
+               }
        }
 
        /**
@@ -78,19 +92,6 @@ abstract class ApiFormatBase extends ApiBase {
                return $this->mFormat;
        }
 
-       /**
-        * Specify whether or not sequences like &amp;quot; should be unescaped
-        * to &quot; . 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'
@@ -102,220 +103,274 @@ abstract class ApiFormatBase extends ApiBase {
        }
 
        /**
-        * 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
+        * Returns true when the special wrapped mode is enabled.
+        * @since 1.27
+        * @return bool
         */
-       function initPrinter($isError) {
-               $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) {
-?>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
-<html>
-<head>
-<?php if ($this->mUnescapeAmps) {
-?>     <title>MediaWiki API</title>
-<?php } else {
-?>     <title>MediaWiki API Result</title>
-<?php } ?>
-</head>
-<body>
-<?php
-
-
-                       if( !$isError ) {
-?>
-<br/>
-<small>
-You are looking at the HTML representation of the <?php echo( $this->mFormat ); ?> format.<br/>
-HTML is good for debugging, but probably is not suitable for your application.<br/>
-See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or
-<a href='<?php echo( $script ); ?>'>API help</a> for more information.
-</small>
-<?php
-
-
-                       }
-?>
-<pre>
-<?php
+       protected function getIsWrappedHtml() {
+               return $this->mIsWrappedHtml;
+       }
 
+       /**
+        * 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;
        }
 
        /**
-        * Finish printing. Closes HTML tags.
+        * 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
         */
-       public function closePrinter() {
-               if ($this->getIsHtml()) {
-?>
+       public function canPrintErrors() {
+               return true;
+       }
 
-</pre>
-</body>
-</html>
-<?php
+       /**
+        * Ignore request parameters, force a default.
+        *
+        * Used as a fallback if errors are being thrown.
+        * @since 1.26
+        */
+       public function forceDefaultParams() {
+               $this->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 );
+               }
 
+               if ( !is_array( $paramSettings ) ) {
+                       return $paramSettings;
+               } elseif ( isset( $paramSettings[self::PARAM_DFLT] ) ) {
+                       return $paramSettings[self::PARAM_DFLT];
+               } else {
+                       return null;
                }
        }
 
        /**
-        * 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
+        * Set the HTTP status code to be used for the response
+        * @since 1.29
+        * @param int $code
         */
-       public function printText($text) {
-               if ($this->getIsHtml())
-                       echo $this->formatHTML($text);
-               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;
+       public function setHttpStatus( $code ) {
+               if ( $this->mDisabled ) {
+                       return;
+               }
+
+               if ( $this->getIsHtml() ) {
+                       $this->mHttpStatus = $code;
+               } else {
+                       $this->getMain()->getRequest()->response()->statusHeader( $code );
                }
        }
 
        /**
-        * Sets whether the pretty-printer should format *bold* and $italics$
-        * @param $help bool
+        * Initialize the printer function and prepare the output headers.
+        * @param bool $unused Always false since 1.25
         */
-       public function setHelp( $help = true ) {
-               $this->mHelp = true;
-       }
+       public function initPrinter( $unused = false ) {
+               if ( $this->mDisabled ) {
+                       return;
+               }
 
-       /**
-       * Prety-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('/\&lt;(!--.*?--|.*?)\&gt;/', '<span style="color:blue;">&lt;\1&gt;</span>', $text);
-               // identify URLs
-               $protos = implode("|", $wgUrlProtocols);
-               # This regex hacks around bug 13218 (&quot; included in the URL)
-               $text = preg_replace("#(($protos).*?)(&quot;)?([ \\'\"<>\n]|&lt;|&gt;|&quot;)#", '<a href="\\1">\\1</a>\\3\\4', $text);
-               // identify requests to api.php
-               $text = preg_replace("#api\\.php\\?[^ \\()<\n\t]+#", '<a href="\\0">\\0</a>', $text);
-               if( $this->mHelp ) {
-                       // make strings inside * bold
-                       $text = preg_replace("#\\*[^<>\n]+\\*#", '<b>\\0</b>', $text);
-                       // make strings inside $ italic
-                       $text = preg_replace("#\\$[^<>\n]+\\$#", '<b><i>\\0</i></b>', $text);
+               $mime = $this->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
                }
 
-               /* 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;(amp|quot|lt|gt);/', '&\1;', $text );
+               $this->getMain()->getRequest()->response()->header( "Content-Type: $mime; charset=utf-8" );
 
-               return $text;
-       }
+               // Set X-Frame-Options API results (T41180)
+               $apiFrameOptions = $this->getConfig()->get( 'ApiFrameOptions' );
+               if ( $apiFrameOptions ) {
+                       $this->getMain()->getRequest()->response()->header( "X-Frame-Options: $apiFrameOptions" );
+               }
 
-       protected function getExamples() {
-               return 'api.php?action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName();
+               // 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}\""
+               );
        }
 
-       public function getDescription() {
-               return $this->getIsHtml() ? ' (pretty-print in HTML)' : '';
-       }
+       /**
+        * Finish printing and output buffered data.
+        */
+       public function closePrinter() {
+               if ( $this->mDisabled ) {
+                       return;
+               }
 
-       public static function getBaseVersion() {
-               return __CLASS__ . ': $Id: ApiFormatBase.php 48521 2009-03-18 19:25:29Z ialex $';
-       }
-}
+               $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()
+                                               )
+                                       );
+                               }
+                       }
 
-/**
- * This printer is used to wrap an instance of the Feed class
- * @ingroup API
- */
-class ApiFormatFeedWrapper extends ApiFormatBase {
+                       if ( Hooks::run( 'ApiFormatHighlight', [ $context, $result, $mime, $format ] ) ) {
+                               $out->addHTML(
+                                       Html::element( 'pre', [ 'class' => 'api-pretty-content' ], $result )
+                               );
+                       }
 
-       public function __construct($main) {
-               parent :: __construct($main, 'feed');
-       }
+                       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
+                       ob_clean();
 
-       /**
-        * 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();
+                       echo $this->getBuffer();
+               }
        }
 
        /**
-        * Feed does its own headers
+        * Append text to the output buffer.
+        * @param string $text
         */
-       public function getMimeType() {
-               return null;
+       public function printText( $text ) {
+               $this->mBuffer .= $text;
        }
 
        /**
-        * Optimization - no need to sanitize data that will not be needed
+        * Get the contents of the buffer.
+        * @return string
         */
-       public function getNeedsRawData() {
-               return true;
+       public function getBuffer() {
+               return $this->mBuffer;
        }
 
-       /**
-        * 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 getAllowedParams() {
+               $ret = [];
+               if ( $this->getIsHtml() ) {
+                       $ret['wrappedhtml'] = [
+                               ApiBase::PARAM_DFLT => false,
+                               ApiBase::PARAM_HELP_MSG => 'apihelp-format-param-wrappedhtml',
+
+                       ];
                }
+               return $ret;
        }
 
-       public function getVersion() {
-               return __CLASS__ . ': $Id: ApiFormatBase.php 48521 2009-03-18 19:25:29Z ialex $';
+       protected function getExamplesMessages() {
+               return [
+                       'action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName()
+                               => [ 'apihelp-format-example-generic', $this->getFormat() ]
+               ];
        }
-}
\ No newline at end of file
+
+       public function getHelpUrls() {
+               return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Data_formats';
+       }
+
+}
+
+/**
+ * For really cool vim folding this needs to be at the end:
+ * vim: foldmarker=@{,@} foldmethod=marker
+ */