MediaWiki  REL1_19
ApiParse.php
Go to the documentation of this file.
00001 <?php
00028 class ApiParse extends ApiBase {
00029         private $section, $text, $pstText = null;
00030 
00031         public function __construct( $main, $action ) {
00032                 parent::__construct( $main, $action );
00033         }
00034 
00035         public function execute() {
00036                 // The data is hot but user-dependent, like page views, so we set vary cookies
00037                 $this->getMain()->setCacheMode( 'anon-public-user-private' );
00038 
00039                 // Get parameters
00040                 $params = $this->extractRequestParams();
00041                 $text = $params['text'];
00042                 $title = $params['title'];
00043                 $page = $params['page'];
00044                 $pageid = $params['pageid'];
00045                 $oldid = $params['oldid'];
00046 
00047                 if ( !is_null( $page ) && ( !is_null( $text ) || $title != 'API' ) ) {
00048                         $this->dieUsage( 'The page parameter cannot be used together with the text and title parameters', 'params' );
00049                 }
00050 
00051                 $prop = array_flip( $params['prop'] );
00052 
00053                 if ( isset( $params['section'] ) ) {
00054                         $this->section = $params['section'];
00055                 } else {
00056                         $this->section = false;
00057                 }
00058 
00059                 // The parser needs $wgTitle to be set, apparently the
00060                 // $title parameter in Parser::parse isn't enough *sigh*
00061                 // TODO: Does this still need $wgTitle?
00062                 global $wgParser, $wgTitle, $wgLang;
00063 
00064                 // Currently unnecessary, code to act as a safeguard against any change in current behaviour of uselang breaks
00065                 $oldLang = null;
00066                 if ( isset( $params['uselang'] ) && $params['uselang'] != $wgLang->getCode() ) {
00067                         $oldLang = $wgLang; // Backup wgLang
00068                         $wgLang = Language::factory( $params['uselang'] );
00069                 }
00070 
00071                 $popts = ParserOptions::newFromContext( $this->getContext() );
00072                 $popts->setTidy( true );
00073                 $popts->enableLimitReport( !$params['disablepp'] );
00074 
00075                 $redirValues = null;
00076 
00077                 // Return result
00078                 $result = $this->getResult();
00079 
00080                 if ( !is_null( $oldid ) || !is_null( $pageid ) || !is_null( $page ) ) {
00081                         if ( !is_null( $oldid ) ) {
00082                                 // Don't use the parser cache
00083                                 $rev = Revision::newFromID( $oldid );
00084                                 if ( !$rev ) {
00085                                         $this->dieUsage( "There is no revision ID $oldid", 'missingrev' );
00086                                 }
00087                                 if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
00088                                         $this->dieUsage( "You don't have permission to view deleted revisions", 'permissiondenied' );
00089                                 }
00090 
00091                                 $titleObj = $rev->getTitle();
00092 
00093                                 $wgTitle = $titleObj;
00094 
00095                                 // If for some reason the "oldid" is actually the current revision, it may be cached
00096                                 if ( $titleObj->getLatestRevID() === intval( $oldid ) )  {
00097                                         // May get from/save to parser cache
00098                                         $p_result = $this->getParsedSectionOrText( $titleObj, $popts, $pageid,
00099                                                  isset( $prop['wikitext'] ) ) ;
00100                                 } else { // This is an old revision, so get the text differently
00101                                         $this->text = $rev->getText( Revision::FOR_THIS_USER, $this->getUser() );
00102 
00103                                         if ( $this->section !== false ) {
00104                                                 $this->text = $this->getSectionText( $this->text, 'r' . $rev->getId() );
00105                                         }
00106 
00107                                         // Should we save old revision parses to the parser cache?
00108                                         $p_result = $wgParser->parse( $this->text, $titleObj, $popts );
00109                                 }
00110                         } else { // Not $oldid, but $pageid or $page
00111                                 if ( $params['redirects'] ) {
00112                                         $reqParams = array(
00113                                                 'action' => 'query',
00114                                                 'redirects' => '',
00115                                         );
00116                                         if ( !is_null ( $pageid ) ) {
00117                                                 $reqParams['pageids'] = $pageid;
00118                                         } else { // $page
00119                                                 $reqParams['titles'] = $page;
00120                                         }
00121                                         $req = new FauxRequest( $reqParams );
00122                                         $main = new ApiMain( $req );
00123                                         $main->execute();
00124                                         $data = $main->getResultData();
00125                                         $redirValues = isset( $data['query']['redirects'] )
00126                                                 ? $data['query']['redirects']
00127                                                 : array();
00128                                         $to = $page;
00129                                         foreach ( (array)$redirValues as $r ) {
00130                                                 $to = $r['to'];
00131                                         }
00132                                         $titleObj = Title::newFromText( $to );
00133                                 } else {
00134                                         if ( !is_null ( $pageid ) ) {
00135                                                 $reqParams['pageids'] = $pageid;
00136                                                 $titleObj = Title::newFromID( $pageid );
00137                                         } else { // $page
00138                                                 $to = $page;
00139                                                 $titleObj = Title::newFromText( $to );
00140                                         }
00141                                 }
00142                                 if ( !is_null ( $pageid ) ) {
00143                                         if ( !$titleObj ) {
00144                                                 // Still throw nosuchpageid error if pageid was provided
00145                                                 $this->dieUsageMsg( array( 'nosuchpageid', $pageid ) );
00146                                         }
00147                                 } elseif ( !$titleObj || !$titleObj->exists() ) {
00148                                         $this->dieUsage( "The page you specified doesn't exist", 'missingtitle' );
00149                                 }
00150                                 $wgTitle = $titleObj;
00151 
00152                                 if ( isset( $prop['revid'] ) ) {
00153                                         $oldid = $titleObj->getLatestRevID();
00154                                 }
00155 
00156                                 // Potentially cached
00157                                 $p_result = $this->getParsedSectionOrText( $titleObj, $popts, $pageid,
00158                                          isset( $prop['wikitext'] ) ) ;
00159                         }
00160                 } else { // Not $oldid, $pageid, $page. Hence based on $text
00161 
00162                         if ( is_null( $text ) ) {
00163                                 $this->dieUsage( 'The text parameter should be passed with the title parameter. Should you be using the "page" parameter instead?', 'params' );
00164                         }
00165                         $this->text = $text;
00166                         $titleObj = Title::newFromText( $title );
00167                         if ( !$titleObj ) {
00168                                 $this->dieUsageMsg( array( 'invalidtitle', $title ) );
00169                         }
00170                         $wgTitle = $titleObj;
00171 
00172                         if ( $this->section !== false ) {
00173                                 $this->text = $this->getSectionText( $this->text, $titleObj->getText() );
00174                         }
00175 
00176                         if ( $params['pst'] || $params['onlypst'] ) {
00177                                 $this->pstText = $wgParser->preSaveTransform( $this->text, $titleObj, $this->getUser(), $popts );
00178                         }
00179                         if ( $params['onlypst'] ) {
00180                                 // Build a result and bail out
00181                                 $result_array = array();
00182                                 $result_array['text'] = array();
00183                                 $result->setContent( $result_array['text'], $this->pstText );
00184                                 if ( isset( $prop['wikitext'] ) ) {
00185                                         $result_array['wikitext'] = array();
00186                                         $result->setContent( $result_array['wikitext'], $this->text );
00187                                 }
00188                                 $result->addValue( null, $this->getModuleName(), $result_array );
00189                                 return;
00190                         }
00191                         // Not cached (save or load)
00192                         $p_result = $wgParser->parse( $params['pst'] ? $this->pstText : $this->text, $titleObj, $popts );
00193                 }
00194 
00195                 $result_array = array();
00196 
00197                 $result_array['title'] = $titleObj->getPrefixedText();
00198 
00199                 if ( !is_null( $oldid ) ) {
00200                         $result_array['revid'] = intval( $oldid );
00201                 }
00202 
00203                 if ( $params['redirects'] && !is_null( $redirValues ) ) {
00204                         $result_array['redirects'] = $redirValues;
00205                 }
00206 
00207                 if ( isset( $prop['text'] ) ) {
00208                         $result_array['text'] = array();
00209                         $result->setContent( $result_array['text'], $p_result->getText() );
00210                 }
00211 
00212                 if ( !is_null( $params['summary'] ) ) {
00213                         $result_array['parsedsummary'] = array();
00214                         $result->setContent( $result_array['parsedsummary'], Linker::formatComment( $params['summary'], $titleObj ) );
00215                 }
00216 
00217                 if ( isset( $prop['langlinks'] ) ) {
00218                         $result_array['langlinks'] = $this->formatLangLinks( $p_result->getLanguageLinks() );
00219                 }
00220                 if ( isset( $prop['languageshtml'] ) ) {
00221                         $languagesHtml = $this->languagesHtml( $p_result->getLanguageLinks() );
00222                         $result_array['languageshtml'] = array();
00223                         $result->setContent( $result_array['languageshtml'], $languagesHtml );
00224                 }
00225                 if ( isset( $prop['categories'] ) ) {
00226                         $result_array['categories'] = $this->formatCategoryLinks( $p_result->getCategories() );
00227                 }
00228                 if ( isset( $prop['categorieshtml'] ) ) {
00229                         $categoriesHtml = $this->categoriesHtml( $p_result->getCategories() );
00230                         $result_array['categorieshtml'] = array();
00231                         $result->setContent( $result_array['categorieshtml'], $categoriesHtml );
00232                 }
00233                 if ( isset( $prop['links'] ) ) {
00234                         $result_array['links'] = $this->formatLinks( $p_result->getLinks() );
00235                 }
00236                 if ( isset( $prop['templates'] ) ) {
00237                         $result_array['templates'] = $this->formatLinks( $p_result->getTemplates() );
00238                 }
00239                 if ( isset( $prop['images'] ) ) {
00240                         $result_array['images'] = array_keys( $p_result->getImages() );
00241                 }
00242                 if ( isset( $prop['externallinks'] ) ) {
00243                         $result_array['externallinks'] = array_keys( $p_result->getExternalLinks() );
00244                 }
00245                 if ( isset( $prop['sections'] ) ) {
00246                         $result_array['sections'] = $p_result->getSections();
00247                 }
00248 
00249                 if ( isset( $prop['displaytitle'] ) ) {
00250                         $result_array['displaytitle'] = $p_result->getDisplayTitle() ?
00251                                                         $p_result->getDisplayTitle() :
00252                                                         $titleObj->getPrefixedText();
00253                 }
00254 
00255                 if ( isset( $prop['headitems'] ) || isset( $prop['headhtml'] ) ) {
00256                         $context = $this->getContext();
00257                         $context->setTitle( $titleObj );
00258                         $context->getOutput()->addParserOutputNoText( $p_result );
00259 
00260                         if ( isset( $prop['headitems'] ) ) {
00261                                 $headItems = $this->formatHeadItems( $p_result->getHeadItems() );
00262 
00263                                 $css = $this->formatCss( $context->getOutput()->buildCssLinksArray() );
00264 
00265                                 $scripts = array( $context->getOutput()->getHeadScripts() );
00266 
00267                                 $result_array['headitems'] = array_merge( $headItems, $css, $scripts );
00268                         }
00269 
00270                         if ( isset( $prop['headhtml'] ) ) {
00271                                 $result_array['headhtml'] = array();
00272                                 $result->setContent( $result_array['headhtml'], $context->getOutput()->headElement( $context->getSkin() ) );
00273                         }
00274                 }
00275 
00276                 if ( isset( $prop['iwlinks'] ) ) {
00277                         $result_array['iwlinks'] = $this->formatIWLinks( $p_result->getInterwikiLinks() );
00278                 }
00279 
00280                 if ( isset( $prop['wikitext'] ) ) {
00281                         $result_array['wikitext'] = array();
00282                         $result->setContent( $result_array['wikitext'], $this->text );
00283                         if ( !is_null( $this->pstText ) ) {
00284                                 $result_array['psttext'] = array();
00285                                 $result->setContent( $result_array['psttext'], $this->pstText );
00286                         }
00287                 }
00288 
00289                 $result_mapping = array(
00290                         'redirects' => 'r',
00291                         'langlinks' => 'll',
00292                         'categories' => 'cl',
00293                         'links' => 'pl',
00294                         'templates' => 'tl',
00295                         'images' => 'img',
00296                         'externallinks' => 'el',
00297                         'iwlinks' => 'iw',
00298                         'sections' => 's',
00299                         'headitems' => 'hi',
00300                 );
00301                 $this->setIndexedTagNames( $result_array, $result_mapping );
00302                 $result->addValue( null, $this->getModuleName(), $result_array );
00303 
00304                 if ( !is_null( $oldLang ) ) {
00305                         $wgLang = $oldLang; // Reset $wgLang to $oldLang
00306                 }
00307         }
00308 
00316         private function getParsedSectionOrText( $titleObj, $popts, $pageId = null, $getWikitext = false ) {
00317                 global $wgParser;
00318 
00319                 $page = WikiPage::factory( $titleObj );
00320 
00321                 if ( $this->section !== false ) {
00322                         $this->text = $this->getSectionText( $page->getRawText(), !is_null( $pageId )
00323                                         ? 'page id ' . $pageId : $titleObj->getText() );
00324 
00325                         // Not cached (save or load)
00326                         return $wgParser->parse( $this->text, $titleObj, $popts );
00327                 } else {
00328                         // Try the parser cache first
00329                         // getParserOutput will save to Parser cache if able
00330                         $pout = $page->getParserOutput( $popts );
00331                         if ( $getWikitext ) {
00332                                 $this->text = $page->getRawText();
00333                         }
00334                         return $pout;
00335                 }
00336         }
00337 
00338         private function getSectionText( $text, $what ) {
00339                 global $wgParser;
00340                 // Not cached (save or load)
00341                 $text = $wgParser->getSection( $text, $this->section, false );
00342                 if ( $text === false ) {
00343                         $this->dieUsage( "There is no section {$this->section} in " . $what, 'nosuchsection' );
00344                 }
00345                 return $text;
00346         }
00347 
00348         private function formatLangLinks( $links ) {
00349                 $result = array();
00350                 foreach ( $links as $link ) {
00351                         $entry = array();
00352                         $bits = explode( ':', $link, 2 );
00353                         $title = Title::newFromText( $link );
00354 
00355                         $entry['lang'] = $bits[0];
00356                         if ( $title ) {
00357                                 $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
00358                         }
00359                         $this->getResult()->setContent( $entry, $bits[1] );
00360                         $result[] = $entry;
00361                 }
00362                 return $result;
00363         }
00364 
00365         private function formatCategoryLinks( $links ) {
00366                 $result = array();
00367                 foreach ( $links as $link => $sortkey ) {
00368                         $entry = array();
00369                         $entry['sortkey'] = $sortkey;
00370                         $this->getResult()->setContent( $entry, $link );
00371                         $result[] = $entry;
00372                 }
00373                 return $result;
00374         }
00375 
00376         private function categoriesHtml( $categories ) {
00377                 $context = $this->getContext();
00378                 $context->getOutput()->addCategoryLinks( $categories );
00379                 return $context->getSkin()->getCategories();
00380         }
00381 
00388         private function languagesHtml( $languages ) {
00389                 wfDeprecated( __METHOD__, '1.18' );
00390 
00391                 global $wgContLang, $wgHideInterlanguageLinks;
00392 
00393                 if ( $wgHideInterlanguageLinks || count( $languages ) == 0 ) {
00394                         return '';
00395                 }
00396 
00397                 $s = htmlspecialchars( wfMsg( 'otherlanguages' ) . wfMsg( 'colon-separator' ) );
00398 
00399                 $langs = array();
00400                 foreach ( $languages as $l ) {
00401                         $nt = Title::newFromText( $l );
00402                         $text = $wgContLang->getLanguageName( $nt->getInterwiki() );
00403 
00404                         $langs[] = Html::element( 'a',
00405                                 array( 'href' => $nt->getFullURL(), 'title' => $nt->getText(), 'class' => "external" ),
00406                                 $text == '' ? $l : $text );
00407                 }
00408 
00409                 $s .= implode( htmlspecialchars( wfMsgExt( 'pipe-separator', 'escapenoentities' ) ), $langs );
00410 
00411                 if ( $wgContLang->isRTL() ) {
00412                         $s = Html::rawElement( 'span', array( 'dir' => "LTR" ), $s );
00413                 }
00414 
00415                 return $s;
00416         }
00417 
00418         private function formatLinks( $links ) {
00419                 $result = array();
00420                 foreach ( $links as $ns => $nslinks ) {
00421                         foreach ( $nslinks as $title => $id ) {
00422                                 $entry = array();
00423                                 $entry['ns'] = $ns;
00424                                 $this->getResult()->setContent( $entry, Title::makeTitle( $ns, $title )->getFullText() );
00425                                 if ( $id != 0 ) {
00426                                         $entry['exists'] = '';
00427                                 }
00428                                 $result[] = $entry;
00429                         }
00430                 }
00431                 return $result;
00432         }
00433 
00434         private function formatIWLinks( $iw ) {
00435                 $result = array();
00436                 foreach ( $iw as $prefix => $titles ) {
00437                         foreach ( array_keys( $titles ) as $title ) {
00438                                 $entry = array();
00439                                 $entry['prefix'] = $prefix;
00440 
00441                                 $title = Title::newFromText( "{$prefix}:{$title}" );
00442                                 if ( $title ) {
00443                                         $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
00444                                 }
00445 
00446                                 $this->getResult()->setContent( $entry, $title->getFullText() );
00447                                 $result[] = $entry;
00448                         }
00449                 }
00450                 return $result;
00451         }
00452 
00453         private function formatHeadItems( $headItems ) {
00454                 $result = array();
00455                 foreach ( $headItems as $tag => $content ) {
00456                         $entry = array();
00457                         $entry['tag'] = $tag;
00458                         $this->getResult()->setContent( $entry, $content );
00459                         $result[] = $entry;
00460                 }
00461                 return $result;
00462         }
00463 
00464         private function formatCss( $css ) {
00465                 $result = array();
00466                 foreach ( $css as $file => $link ) {
00467                         $entry = array();
00468                         $entry['file'] = $file;
00469                         $this->getResult()->setContent( $entry, $link );
00470                         $result[] = $entry;
00471                 }
00472                 return $result;
00473         }
00474 
00475         private function setIndexedTagNames( &$array, $mapping ) {
00476                 foreach ( $mapping as $key => $name ) {
00477                         if ( isset( $array[$key] ) ) {
00478                                 $this->getResult()->setIndexedTagName( $array[$key], $name );
00479                         }
00480                 }
00481         }
00482 
00483         public function getAllowedParams() {
00484                 return array(
00485                         'title' => array(
00486                                 ApiBase::PARAM_DFLT => 'API',
00487                         ),
00488                         'text' => null,
00489                         'summary' => null,
00490                         'page' => null,
00491                         'pageid' => array(
00492                                 ApiBase::PARAM_TYPE => 'integer',
00493                         ),
00494                         'redirects' => false,
00495                         'oldid' => array(
00496                                 ApiBase::PARAM_TYPE => 'integer',
00497                         ),
00498                         'prop' => array(
00499                                 ApiBase::PARAM_DFLT => 'text|langlinks|categories|links|templates|images|externallinks|sections|revid|displaytitle',
00500                                 ApiBase::PARAM_ISMULTI => true,
00501                                 ApiBase::PARAM_TYPE => array(
00502                                         'text',
00503                                         'langlinks',
00504                                         'languageshtml',
00505                                         'categories',
00506                                         'categorieshtml',
00507                                         'links',
00508                                         'templates',
00509                                         'images',
00510                                         'externallinks',
00511                                         'sections',
00512                                         'revid',
00513                                         'displaytitle',
00514                                         'headitems',
00515                                         'headhtml',
00516                                         'iwlinks',
00517                                         'wikitext',
00518                                 )
00519                         ),
00520                         'pst' => false,
00521                         'onlypst' => false,
00522                         'uselang' => null,
00523                         'section' => null,
00524                         'disablepp' => false,
00525                 );
00526         }
00527 
00528         public function getParamDescription() {
00529                 $p = $this->getModulePrefix();
00530                 return array(
00531                         'text' => 'Wikitext to parse',
00532                         'summary' => 'Summary to parse',
00533                         'redirects' => "If the {$p}page or the {$p}pageid parameter is set to a redirect, resolve it",
00534                         'title' => 'Title of page the text belongs to',
00535                         'page' => "Parse the content of this page. Cannot be used together with {$p}text and {$p}title",
00536                         'pageid' => "Parse the content of this page. Overrides {$p}page",
00537                         'oldid' => "Parse the content of this revision. Overrides {$p}page and {$p}pageid",
00538                         'prop' => array(
00539                                 'Which pieces of information to get',
00540                                 ' text           - Gives the parsed text of the wikitext',
00541                                 ' langlinks      - Gives the language links in the parsed wikitext',
00542                                 ' categories     - Gives the categories in the parsed wikitext',
00543                                 ' categorieshtml - Gives the HTML version of the categories',
00544                                 ' languageshtml  - Gives the HTML version of the language links',
00545                                 ' links          - Gives the internal links in the parsed wikitext',
00546                                 ' templates      - Gives the templates in the parsed wikitext',
00547                                 ' images         - Gives the images in the parsed wikitext',
00548                                 ' externallinks  - Gives the external links in the parsed wikitext',
00549                                 ' sections       - Gives the sections in the parsed wikitext',
00550                                 ' revid          - Adds the revision ID of the parsed page',
00551                                 ' displaytitle   - Adds the title of the parsed wikitext',
00552                                 ' headitems      - Gives items to put in the <head> of the page',
00553                                 ' headhtml       - Gives parsed <head> of the page',
00554                                 ' iwlinks        - Gives interwiki links in the parsed wikitext',
00555                                 ' wikitext       - Gives the original wikitext that was parsed',
00556                         ),
00557                         'pst' => array(
00558                                 'Do a pre-save transform on the input before parsing it',
00559                                 'Ignored if page, pageid or oldid is used'
00560                         ),
00561                         'onlypst' => array(
00562                                 'Do a pre-save transform (PST) on the input, but don\'t parse it',
00563                                 'Returns the same wikitext, after a PST has been applied. Ignored if page, pageid or oldid is used'
00564                         ),
00565                         'uselang' => 'Which language to parse the request in',
00566                         'section' => 'Only retrieve the content of this section number',
00567                         'disablepp' => 'Disable the PP Report from the parser output',
00568                 );
00569         }
00570 
00571         public function getDescription() {
00572                 return 'Parses wikitext and returns parser output';
00573         }
00574 
00575         public function getPossibleErrors() {
00576                 return array_merge( parent::getPossibleErrors(), array(
00577                         array( 'code' => 'params', 'info' => 'The page parameter cannot be used together with the text and title parameters' ),
00578                         array( 'code' => 'params', 'info' => 'The text parameter should be passed with the title parameter. Should you be using the "page" parameter instead?' ),
00579                         array( 'code' => 'missingrev', 'info' => 'There is no revision ID oldid' ),
00580                         array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted revisions' ),
00581                         array( 'code' => 'missingtitle', 'info' => 'The page you specified doesn\'t exist' ),
00582                         array( 'code' => 'nosuchsection', 'info' => 'There is no section sectionnumber in page' ),
00583                         array( 'nosuchpageid' ),
00584                         array( 'invalidtitle', 'title' ),
00585                 ) );
00586         }
00587 
00588         public function getExamples() {
00589                 return array(
00590                         'api.php?action=parse&text={{Project:Sandbox}}'
00591                 );
00592         }
00593 
00594         public function getHelpUrls() {
00595                 return 'https://www.mediawiki.org/wiki/API:Parsing_wikitext#parse';
00596         }
00597 
00598         public function getVersion() {
00599                 return __CLASS__ . ': $Id$';
00600         }
00601 }