Followup r75334. Change to int == string, rather than int === string (not going to...
[lhc/web/wiklou.git] / includes / api / ApiParse.php
1 <?php
2 /**
3 *
4 *
5 * Created on Dec 01, 2007
6 *
7 * Copyright © 2007 Yuri Astrakhan <Firstname><Lastname>@gmail.com
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 * http://www.gnu.org/copyleft/gpl.html
23 *
24 * @file
25 */
26
27 if ( !defined( 'MEDIAWIKI' ) ) {
28 // Eclipse helper - will be ignored in production
29 require_once( "ApiBase.php" );
30 }
31
32 /**
33 * @ingroup API
34 */
35 class ApiParse extends ApiBase {
36
37 private $section;
38
39 public function __construct( $main, $action ) {
40 parent::__construct( $main, $action );
41 }
42
43 public function execute() {
44 // The data is hot but user-dependent, like page views, so we set vary cookies
45 $this->getMain()->setCacheMode( 'anon-public-user-private' );
46
47 // Get parameters
48 $params = $this->extractRequestParams();
49 $text = $params['text'];
50 $title = $params['title'];
51 $page = $params['page'];
52 $pageid = $params['pageid'];
53 $oldid = $params['oldid'];
54
55 if ( !is_null( $page ) && ( !is_null( $text ) || $title != 'API' ) ) {
56 $this->dieUsage( 'The page parameter cannot be used together with the text and title parameters', 'params' );
57 }
58 $prop = array_flip( $params['prop'] );
59
60 if ( isset( $params['section'] ) ) {
61 $this->section = $params['section'];
62 } else {
63 $this->section = false;
64 }
65
66 // The parser needs $wgTitle to be set, apparently the
67 // $title parameter in Parser::parse isn't enough *sigh*
68 global $wgParser, $wgUser, $wgTitle, $wgEnableParserCache, $wgLang;
69
70 // Currently unnecessary, code to act as a safeguard against any change in current behaviour of uselang breaks
71 $oldLang = null;
72 if ( isset( $params['uselang'] ) && $params['uselang'] != $wgLang->getCode() ) {
73 $oldLang = $wgLang; // Backup wgLang
74 $wgLang = Language::factory( $params['uselang'] );
75 }
76
77 $popts = new ParserOptions();
78 $popts->setTidy( true );
79 $popts->enableLimitReport( !$params['disablepp'] );
80 $redirValues = null;
81 if ( !is_null( $oldid ) || !is_null( $pageid ) || !is_null( $page ) ) {
82 if ( !is_null( $oldid ) ) {
83 // Don't use the parser cache
84 $rev = Revision::newFromID( $oldid );
85 if ( !$rev ) {
86 $this->dieUsage( "There is no revision ID $oldid", 'missingrev' );
87 }
88 if ( !$rev->userCan( Revision::DELETED_TEXT ) ) {
89 $this->dieUsage( "You don't have permission to view deleted revisions", 'permissiondenied' );
90 }
91
92 $titleObj = $rev->getTitle();
93
94 $wgTitle = $titleObj;
95
96 // If for some reason the "oldid" is actually the current revision, it may be cached
97 if ( $titleObj->getLatestRevID() == $oldid ) {
98 $p_result = false;
99 $pcache = ParserCache::singleton();
100
101 $articleObj = new Article( $titleObj );
102
103 if ( $wgEnableParserCache ) {
104 $p_result = $pcache->get( $articleObj, $popts );
105 }
106 if ( !$p_result ) {
107 $text = $rev->getText( Revision::FOR_THIS_USER );
108 $p_result = $wgParser->parse( $text, $titleObj, $popts );
109
110 if ( $wgEnableParserCache ) {
111 $pcache->save( $p_result, $articleObj, $popts );
112 }
113 }
114 } else {
115 $text = $rev->getText( Revision::FOR_THIS_USER );
116
117 $wgTitle = $titleObj;
118
119 if ( $this->section !== false ) {
120 $text = $this->getSectionText( $text, 'r' . $rev );
121 }
122
123 $p_result = $wgParser->parse( $text, $titleObj, $popts );
124 }
125 } else {
126 if ( !is_null ( $pageid ) ) {
127 $titleObj = Title::newFromID( $pageid );
128
129 if ( !$titleObj ) {
130 $this->dieUsageMsg( array( 'nosuchpageid', $pageid ) );
131 }
132 } else {
133 if ( $params['redirects'] ) {
134 $req = new FauxRequest( array(
135 'action' => 'query',
136 'redirects' => '',
137 'titles' => $page
138 ) );
139 $main = new ApiMain( $req );
140 $main->execute();
141 $data = $main->getResultData();
142 $redirValues = @$data['query']['redirects'];
143 $to = $page;
144 foreach ( (array)$redirValues as $r ) {
145 $to = $r['to'];
146 }
147 } else {
148 $to = $page;
149 }
150 $titleObj = Title::newFromText( $to );
151 if ( !$titleObj ) {
152 $this->dieUsage( "The page you specified doesn't exist", 'missingtitle' );
153 }
154 }
155 $wgTitle = $titleObj;
156
157 $articleObj = new Article( $titleObj );
158 if ( isset( $prop['revid'] ) ) {
159 $oldid = $articleObj->getRevIdFetched();
160 }
161
162 if ( $this->section !== false ) {
163 $text = $this->getSectionText( $text, !is_null ( $pageid )
164 ? 'page id ' . $pageid : $titleObj->getText() );
165
166 $p_result = $wgParser->parse( $text, $titleObj, $popts );
167 } else {
168 // Try the parser cache first
169 $p_result = false;
170 $pcache = ParserCache::singleton();
171 if ( $wgEnableParserCache ) {
172 $p_result = $pcache->get( $articleObj, $popts );
173 }
174 if ( !$p_result ) {
175 $p_result = $wgParser->parse( $articleObj->getContent(), $titleObj, $popts );
176
177 if ( $wgEnableParserCache ) {
178 $pcache->save( $p_result, $articleObj, $popts );
179 }
180 }
181 }
182 }
183 } else {
184 $titleObj = Title::newFromText( $title );
185 if ( !$titleObj ) {
186 $titleObj = Title::newFromText( 'API' );
187 }
188 $wgTitle = $titleObj;
189
190 if ( $this->section !== false ) {
191 $text = $this->getSectionText( $text, $titleObj->getText() );
192 }
193
194 if ( $params['pst'] || $params['onlypst'] ) {
195 $text = $wgParser->preSaveTransform( $text, $titleObj, $wgUser, $popts );
196 }
197 if ( $params['onlypst'] ) {
198 // Build a result and bail out
199 $result_array['text'] = array();
200 $this->getResult()->setContent( $result_array['text'], $text );
201 $this->getResult()->addValue( null, $this->getModuleName(), $result_array );
202 return;
203 }
204 $p_result = $wgParser->parse( $text, $titleObj, $popts );
205 }
206
207 // Return result
208 $result = $this->getResult();
209 $result_array = array();
210 if ( $params['redirects'] && !is_null( $redirValues ) ) {
211 $result_array['redirects'] = $redirValues;
212 }
213
214 if ( isset( $prop['text'] ) ) {
215 $result_array['text'] = array();
216 $result->setContent( $result_array['text'], $p_result->getText() );
217 }
218
219 if ( !is_null( $params['summary'] ) ) {
220 $result_array['parsedsummary'] = array();
221 $result->setContent( $result_array['parsedsummary'], $wgUser->getSkin()->formatComment( $params['summary'], $titleObj ) );
222 }
223
224 if ( isset( $prop['langlinks'] ) ) {
225 $result_array['langlinks'] = $this->formatLangLinks( $p_result->getLanguageLinks() );
226 }
227 if ( isset( $prop['languageshtml'] ) ) {
228 $languagesHtml = $this->languagesHtml( $p_result->getLanguageLinks() );
229 $result_array['languageshtml'] = array();
230 $result->setContent( $result_array['languageshtml'], $languagesHtml );
231 }
232 if ( isset( $prop['categories'] ) ) {
233 $result_array['categories'] = $this->formatCategoryLinks( $p_result->getCategories() );
234 }
235 if ( isset( $prop['categorieshtml'] ) ) {
236 $categoriesHtml = $this->categoriesHtml( $p_result->getCategories() );
237 $result_array['categorieshtml'] = array();
238 $result->setContent( $result_array['categorieshtml'], $categoriesHtml );
239 }
240 if ( isset( $prop['links'] ) ) {
241 $result_array['links'] = $this->formatLinks( $p_result->getLinks() );
242 }
243 if ( isset( $prop['templates'] ) ) {
244 $result_array['templates'] = $this->formatLinks( $p_result->getTemplates() );
245 }
246 if ( isset( $prop['images'] ) ) {
247 $result_array['images'] = array_keys( $p_result->getImages() );
248 }
249 if ( isset( $prop['externallinks'] ) ) {
250 $result_array['externallinks'] = array_keys( $p_result->getExternalLinks() );
251 }
252 if ( isset( $prop['sections'] ) ) {
253 $result_array['sections'] = $p_result->getSections();
254 }
255
256 if ( isset( $prop['displaytitle'] ) ) {
257 $result_array['displaytitle'] = $p_result->getDisplayTitle() ?
258 $p_result->getDisplayTitle() :
259 $titleObj->getPrefixedText();
260 }
261
262 if ( isset( $prop['headitems'] ) || isset( $prop['headhtml'] ) ) {
263 $out = new OutputPage;
264 $out->addParserOutputNoText( $p_result );
265 $userSkin = $wgUser->getSkin();
266 }
267
268 if ( isset( $prop['headitems'] ) ) {
269 $headItems = $this->formatHeadItems( $p_result->getHeadItems() );
270
271 $userSkin->setupUserCss( $out );
272 $css = $this->formatCss( $out->buildCssLinksArray() );
273
274 $scripts = array( $out->getHeadScripts( $userSkin ) );
275
276 $result_array['headitems'] = array_merge( $headItems, $css, $scripts );
277 }
278
279 if ( isset( $prop['headhtml'] ) ) {
280 $result_array['headhtml'] = array();
281 $result->setContent( $result_array['headhtml'], $out->headElement( $userSkin ) );
282 }
283
284 if ( isset( $prop['iwlinks'] ) ) {
285 $result_array['iwlinks'] = $this->formatIWLinks( $p_result->getInterwikiLinks() );
286 }
287
288 if ( !is_null( $oldid ) ) {
289 $result_array['revid'] = intval( $oldid );
290 }
291
292 $result_mapping = array(
293 'redirects' => 'r',
294 'langlinks' => 'll',
295 'categories' => 'cl',
296 'links' => 'pl',
297 'templates' => 'tl',
298 'images' => 'img',
299 'externallinks' => 'el',
300 'iwlinks' => 'iw',
301 'sections' => 's',
302 'headitems' => 'hi',
303 );
304 $this->setIndexedTagNames( $result_array, $result_mapping );
305 $result->addValue( null, $this->getModuleName(), $result_array );
306
307 if ( !is_null( $oldLang ) ) {
308 $wgLang = $oldLang; // Reset $wgLang to $oldLang
309 }
310 }
311
312 private function getSectionText( $text, $what ) {
313 global $wgParser;
314 $text = $wgParser->getSection( $text, $this->section, false );
315 if ( $text === false ) {
316 $this->dieUsage( "There is no section {$this->section} in " . $what, 'nosuchsection' );
317 }
318 return $text;
319 }
320
321 private function formatLangLinks( $links ) {
322 $result = array();
323 foreach ( $links as $link ) {
324 $entry = array();
325 $bits = explode( ':', $link, 2 );
326 $entry['lang'] = $bits[0];
327 $this->getResult()->setContent( $entry, $bits[1] );
328 $result[] = $entry;
329 }
330 return $result;
331 }
332
333 private function formatCategoryLinks( $links ) {
334 $result = array();
335 foreach ( $links as $link => $sortkey ) {
336 $entry = array();
337 $entry['sortkey'] = $sortkey;
338 $this->getResult()->setContent( $entry, $link );
339 $result[] = $entry;
340 }
341 return $result;
342 }
343
344 private function categoriesHtml( $categories ) {
345 global $wgOut, $wgUser;
346 $wgOut->addCategoryLinks( $categories );
347 $sk = $wgUser->getSkin();
348 return $sk->getCategories();
349 }
350
351 private function languagesHtml( $languages ) {
352 global $wgOut, $wgUser;
353 $wgOut->setLanguageLinks( $languages );
354 $sk = $wgUser->getSkin();
355 return $sk->otherLanguages();
356 }
357
358 private function formatLinks( $links ) {
359 $result = array();
360 foreach ( $links as $ns => $nslinks ) {
361 foreach ( $nslinks as $title => $id ) {
362 $entry = array();
363 $entry['ns'] = $ns;
364 $this->getResult()->setContent( $entry, Title::makeTitle( $ns, $title )->getFullText() );
365 if ( $id != 0 ) {
366 $entry['exists'] = '';
367 }
368 $result[] = $entry;
369 }
370 }
371 return $result;
372 }
373
374 private function formatIWLinks( $iw ) {
375 $result = array();
376 foreach ( $iw as $prefix => $titles ) {
377 foreach ( array_keys( $titles ) as $title ) {
378 $entry = array();
379 $entry['prefix'] = $prefix;
380
381 $title = Title::newFromText( "{$prefix}:{$title}" );
382 if ( $title ) {
383 $entry['url'] = $title->getFullURL();
384 }
385
386 $this->getResult()->setContent( $entry, $title->getFullText() );
387 $result[] = $entry;
388 }
389 }
390 return $result;
391 }
392
393 private function formatHeadItems( $headItems ) {
394 $result = array();
395 foreach ( $headItems as $tag => $content ) {
396 $entry = array();
397 $entry['tag'] = $tag;
398 $this->getResult()->setContent( $entry, $content );
399 $result[] = $entry;
400 }
401 return $result;
402 }
403
404 private function formatCss( $css ) {
405 $result = array();
406 foreach ( $css as $file => $link ) {
407 $entry = array();
408 $entry['file'] = $file;
409 $this->getResult()->setContent( $entry, $link );
410 $result[] = $entry;
411 }
412 return $result;
413 }
414
415 private function setIndexedTagNames( &$array, $mapping ) {
416 foreach ( $mapping as $key => $name ) {
417 if ( isset( $array[$key] ) ) {
418 $this->getResult()->setIndexedTagName( $array[$key], $name );
419 }
420 }
421 }
422
423 public function getAllowedParams() {
424 return array(
425 'title' => array(
426 ApiBase::PARAM_DFLT => 'API',
427 ),
428 'text' => null,
429 'summary' => null,
430 'page' => null,
431 'pageid' => null,
432 'redirects' => false,
433 'oldid' => null,
434 'prop' => array(
435 ApiBase::PARAM_DFLT => 'text|langlinks|categories|links|templates|images|externallinks|sections|revid|displaytitle',
436 ApiBase::PARAM_ISMULTI => true,
437 ApiBase::PARAM_TYPE => array(
438 'text',
439 'langlinks',
440 'languageshtml',
441 'categories',
442 'categorieshtml',
443 'links',
444 'templates',
445 'images',
446 'externallinks',
447 'sections',
448 'revid',
449 'displaytitle',
450 'headitems',
451 'headhtml',
452 'iwlinks',
453 )
454 ),
455 'pst' => false,
456 'onlypst' => false,
457 'uselang' => null,
458 'section' => null,
459 'disablepp' => false,
460 );
461 }
462
463 public function getParamDescription() {
464 $p = $this->getModulePrefix();
465 return array(
466 'text' => 'Wikitext to parse',
467 'summary' => 'Summary to parse',
468 'redirects' => "If the {$p}page parameter is set to a redirect, resolve it",
469 'title' => 'Title of page the text belongs to',
470 'page' => "Parse the content of this page. Cannot be used together with {$p}text and {$p}title",
471 'pageid' => "Parse the content of this page. Overrides {$p}page",
472 'oldid' => "Parse the content of this revision. Overrides {$p}page and {$p}pageid",
473 'prop' => array(
474 'Which pieces of information to get',
475 ' text - Gives the parsed text of the wikitext',
476 ' langlinks - Gives the language links in the parsed wikitext',
477 ' categories - Gives the categories in the parsed wikitext',
478 ' categorieshtml - Gives the HTML version of the categories',
479 ' languageshtml - Gives the HTML version of the language links',
480 ' links - Gives the internal links in the parsed wikitext',
481 ' templates - Gives the templates in the parsed wikitext',
482 ' images - Gives the images in the parsed wikitext',
483 ' externallinks - Gives the external links in the parsed wikitext',
484 ' sections - Gives the sections in the parsed wikitext',
485 ' revid - Adds the revision ID of the parsed page',
486 ' displaytitle - Adds the title of the parsed wikitext',
487 ' headitems - Gives items to put in the <head> of the page',
488 ' headhtml - Gives parsed <head> of the page',
489 ' iwlinks - Gives interwiki links in the parsed wikitext',
490 'NOTE: Section tree is only generated if there are more than 4 sections, or if the __TOC__ keyword is present'
491 ),
492 'pst' => array(
493 'Do a pre-save transform on the input before parsing it',
494 'Ignored if page, pageid or oldid is used'
495 ),
496 'onlypst' => array(
497 'Do a pre-save transform (PST) on the input, but don\'t parse it',
498 'Returns the same wikitext, after a PST has been applied. Ignored if page, pageid or oldid is used'
499 ),
500 'uselang' => 'Which language to parse the request in',
501 'section' => 'Only retrieve the content of this section number',
502 'disablepp' => 'Disable the PP Report from the parser output',
503 );
504 }
505
506 public function getDescription() {
507 return 'This module parses wikitext and returns parser output';
508 }
509
510 public function getPossibleErrors() {
511 return array_merge( parent::getPossibleErrors(), array(
512 array( 'code' => 'params', 'info' => 'The page parameter cannot be used together with the text and title parameters' ),
513 array( 'code' => 'missingrev', 'info' => 'There is no revision ID oldid' ),
514 array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted revisions' ),
515 array( 'code' => 'missingtitle', 'info' => 'The page you specified doesn\'t exist' ),
516 array( 'code' => 'nosuchsection', 'info' => 'There is no section sectionnumber in page' ),
517 array( 'nosuchpageid' ),
518 ) );
519 }
520
521 protected function getExamples() {
522 return array(
523 'api.php?action=parse&text={{Project:Sandbox}}'
524 );
525 }
526
527 public function getVersion() {
528 return __CLASS__ . ': $Id$';
529 }
530 }