]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blobdiff - includes/api/ApiFormatBase.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / includes / api / ApiFormatBase.php
index 861310d26dc3eae8b408a988455676fe9fd46219..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.
- * 
- * @addtogroup API
+ *
+ * @ingroup API
  */
 abstract class ApiFormatBase extends ApiBase {
-
-       private $mIsHtml, $mFormat;
+       private $mIsHtml, $mFormat, $mUnescapeAmps, $mHelp;
+       private $mBuffer, $mDisabled = false;
+       private $mIsWrappedHtml = false;
+       private $mHttpStatus = false;
+       protected $mForceDefaultParams = false;
 
        /**
-       * Create a new instance of the formatter.
-       * If the format name ends with 'fm', wrap its output in the proper HTML.
-       */
-       public function __construct($main, $format) {
-               parent :: __construct($main, $format);
+        * If $format ends with 'fm', pretty-print the output in HTML.
+        * @param ApiMain $main
+        * @param string $format Format name
+        */
+       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->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();
+
+       /**
+        * 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 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";
+               }
+       }
 
        /**
-        * If formatter outputs data results as is, the results must first be sanitized.
-        * An XML formatter on the other hand uses special tags, such as "_element" for special handling,
-        * and thus needs to override this function to return true.  
+        * Get the internal format name
+        * @return string
         */
-       public function getNeedsRawData() {
-               return false;
+       public function getFormat() {
+               return $this->mFormat;
        }
 
        /**
-        * Returns true when an HTML filtering printer should be used.
-        * The default implementation assumes that formats ending with 'fm' 
-        * should be formatted in HTML. 
+        * Returns true when the HTML pretty-printer should be used.
+        * The default implementation assumes that formats ending with 'fm'
+        * should be formatted in HTML.
+        * @return bool
         */
        public function getIsHtml() {
                return $this->mIsHtml;
        }
 
        /**
-        * Initialize the printer function and prepares 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
+        * 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>
-       <title>MediaWiki API</title>
-</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;
        }
 
        /**
-        * Finish printing. Closes HTML tags.
+        * Disable the formatter.
+        *
+        * This causes calls to initPrinter() and closePrinter() to be ignored.
         */
-       public function closePrinter() {
-               if ($this->getIsHtml()) {
-?>
-
-</pre>
-</body>
-</html>
-<?php
-
-
-               }
+       public function disable() {
+               $this->mDisabled = true;
        }
 
        /**
-        * 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'.
+        * Whether the printer is disabled
+        * @return bool
         */
-       public function printText($text) {
-               if ($this->getIsHtml())
-                       echo $this->formatHTML($text);
-               else
-                       echo $text;
+       public function isDisabled() {
+               return $this->mDisabled;
        }
 
        /**
-       * Prety-print various elements in HTML format, such as xml tags and URLs.
-       * This method also replaces any '<' with &lt;
-       */
-       protected function formatHTML($text) {
-               // 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 = "http|https|ftp|gopher";
-               $text = ereg_replace("($protos)://[^ \\'\"()<\n]+", '<a href="\\0">\\0</a>', $text);
-               // identify requests to api.php
-               $text = ereg_replace("api\\.php\\?[^ \\()<\n\t]+", '<a href="\\0">\\0</a>', $text);
-               // make strings inside * bold
-               $text = ereg_replace("\\*[^<>\n]+\\*", '<b>\\0</b>', $text);
-               // make strings inside $ italic
-               $text = ereg_replace("\\$[^<>\n]+\\$", '<b><i>\\0</i></b>', $text);
-
-               return $text;
+        * 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 canPrintErrors() {
+               return true;
        }
 
        /**
-        * Returns usage examples for this format.
+        * Ignore request parameters, force a default.
+        *
+        * Used as a fallback if errors are being thrown.
+        * @since 1.26
         */
-       protected function getExamples() {
-               return 'api.php?action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName();
+       public function forceDefaultParams() {
+               $this->mForceDefaultParams = true;
        }
 
-       protected function getDescription() {
-               return $this->getIsHtml() ? ' (pretty-print in HTML)' : '';
-       }
+       /**
+        * 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 );
+               }
 
-       public static function getBaseVersion() {
-               return __CLASS__ . ': $Id: ApiFormatBase.php 25746 2007-09-10 21:36:51Z brion $';
+               if ( !is_array( $paramSettings ) ) {
+                       return $paramSettings;
+               } elseif ( isset( $paramSettings[self::PARAM_DFLT] ) ) {
+                       return $paramSettings[self::PARAM_DFLT];
+               } else {
+                       return null;
+               }
        }
-}
 
-/**
- * This printer is used to wrap an instance of the Feed class 
- * @addtogroup API
- */
-class ApiFormatFeedWrapper extends ApiFormatBase {
+       /**
+        * Set the HTTP status code to be used for the response
+        * @since 1.29
+        * @param int $code
+        */
+       public function setHttpStatus( $code ) {
+               if ( $this->mDisabled ) {
+                       return;
+               }
 
-       public function __construct($main) {
-               parent :: __construct($main, 'feed');
+               if ( $this->getIsHtml() ) {
+                       $this->mHttpStatus = $code;
+               } else {
+                       $this->getMain()->getRequest()->response()->statusHeader( $code );
+               }
        }
 
        /**
-        * Call this method to initialize output data. See self::execute()
+        * Initialize the printer function and prepare the output headers.
+        * @param bool $unused Always false since 1.25
         */
-       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
-               $data = & $result->getData();
-               $data['_feed'] = $feed;
-               $data['_feeditems'] = $feedItems;
+       public function initPrinter( $unused = false ) {
+               if ( $this->mDisabled ) {
+                       return;
+               }
+
+               $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
+               }
+
+               $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}\""
+               );
        }
 
        /**
-        * Feed does its own headers
+        * Finish printing and output buffered data.
         */
-       public function getMimeType() {
-               return null;
+       public function closePrinter() {
+               if ( $this->mDisabled ) {
+                       return;
+               }
+
+               $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
+                       ob_clean();
+
+                       echo $this->getBuffer();
+               }
        }
 
        /**
-        * Optimization - no need to sanitize data that will not be needed
+        * Append text to the output buffer.
+        * @param string $text
         */
-       public function getNeedsRawData() {
-               return true;
+       public function printText( $text ) {
+               $this->mBuffer .= $text;
        }
 
        /**
-        * 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
+        * Get the contents of the buffer.
+        * @return string
         */
-       public function execute() {
-               $data = $this->getResultData();
-               if (isset ($data['_feed']) && isset ($data['_feeditems'])) {
-                       $feed = $data['_feed'];
-                       $items = $data['_feeditems'];
+       public function getBuffer() {
+               return $this->mBuffer;
+       }
 
-                       $feed->outHeader();
-                       foreach ($items as & $item)
-                               $feed->outItem($item);
-                       $feed->outFooter();
-               } else {
-                       // Error has occured, print something usefull
-                       // TODO: make this error more informative using ApiBase :: dieDebug() or similar
-                       wfHttpError(500, 'Internal Server Error', '');
+       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 25746 2007-09-10 21:36:51Z brion $';
+
+       protected function getExamplesMessages() {
+               return [
+                       'action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName()
+                               => [ 'apihelp-format-example-generic', $this->getFormat() ]
+               ];
        }
+
+       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
+ */