Cite_body.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760
  1. <?php
  2. /**#@+
  3. * A parser extension that adds two tags, <ref> and <references> for adding
  4. * citations to pages
  5. *
  6. * @addtogroup Extensions
  7. *
  8. * @link http://meta.wikimedia.org/wiki/Cite/Cite.php Documentation
  9. * @link http://www.w3.org/TR/html4/struct/text.html#edef-CITE <cite> definition in HTML
  10. * @link http://www.w3.org/TR/2005/WD-xhtml2-20050527/mod-text.html#edef_text_cite <cite> definition in XHTML 2.0
  11. *
  12. * @bug 4579
  13. *
  14. * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
  15. * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
  16. * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
  17. */
  18. class Cite {
  19. /**#@+
  20. * @access private
  21. */
  22. /**
  23. * Datastructure representing <ref> input, in the format of:
  24. * <code>
  25. * array(
  26. * 'user supplied' => array(
  27. * 'text' => 'user supplied reference & key',
  28. * 'count' => 1, // occurs twice
  29. * 'number' => 1, // The first reference, we want
  30. * // all occourances of it to
  31. * // use the same number
  32. * ),
  33. * 0 => 'Anonymous reference',
  34. * 1 => 'Another anonymous reference',
  35. * 'some key' => array(
  36. * 'text' => 'this one occurs once'
  37. * 'count' => 0,
  38. * 'number' => 4
  39. * ),
  40. * 3 => 'more stuff'
  41. * );
  42. * </code>
  43. *
  44. * This works because:
  45. * * PHP's datastructures are guarenteed to be returned in the
  46. * order that things are inserted into them (unless you mess
  47. * with that)
  48. * * User supplied keys can't be integers, therefore avoiding
  49. * conflict with anonymous keys
  50. *
  51. * @var array
  52. **/
  53. var $mRefs = array();
  54. /**
  55. * Count for user displayed output (ref[1], ref[2], ...)
  56. *
  57. * @var int
  58. */
  59. var $mOutCnt = 0;
  60. var $mGroupCnt = array();
  61. /**
  62. * Internal counter for anonymous references, separate from
  63. * $mOutCnt because anonymous references won't increment it,
  64. * but will incremement $mOutCnt
  65. *
  66. * @var int
  67. */
  68. var $mInCnt = 0;
  69. /**
  70. * The backlinks, in order, to pass as $3 to
  71. * 'cite_references_link_many_format', defined in
  72. * 'cite_references_link_many_format_backlink_labels
  73. *
  74. * @var array
  75. */
  76. var $mBacklinkLabels;
  77. /**
  78. * @var object
  79. */
  80. var $mParser;
  81. /**
  82. * True when a <ref> or <references> tag is being processed.
  83. * Used to avoid infinite recursion
  84. *
  85. * @var boolean
  86. */
  87. var $mInCite = false;
  88. /**#@-*/
  89. /**
  90. * Constructor
  91. */
  92. function Cite() {
  93. $this->setHooks();
  94. }
  95. /**#@+ @access private */
  96. /**
  97. * Callback function for <ref>
  98. *
  99. * @param string $str Input
  100. * @param array $argv Arguments
  101. * @return string
  102. */
  103. function ref( $str, $argv, $parser ) {
  104. wfLoadExtensionMessages( 'Cite' );
  105. if ( $this->mInCite ) {
  106. return htmlspecialchars( "<ref>$str</ref>" );
  107. } else {
  108. $this->mInCite = true;
  109. $ret = $this->guardedRef( $str, $argv, $parser );
  110. $this->mInCite = false;
  111. return $ret;
  112. }
  113. }
  114. function guardedRef( $str, $argv, $parser, $default_group=CITE_DEFAULT_GROUP ) {
  115. $this->mParser = $parser;
  116. # The key here is the "name" attribute.
  117. list($key,$group) = $this->refArg( $argv );
  118. if( $str === '' ) {
  119. # <ref ...></ref>. This construct is invalid if
  120. # it's a contentful ref, but OK if it's a named duplicate and should
  121. # be equivalent <ref ... />, for compatability with #tag.
  122. if ( $key == false )
  123. return $this->error( 'cite_error_ref_no_input' );
  124. else
  125. $str = null;
  126. }
  127. if( $key === false ) {
  128. # TODO: Comment this case; what does this condition mean?
  129. return $this->error( 'cite_error_ref_too_many_keys' );
  130. }
  131. if( $str === null and $key === null ) {
  132. # Something like <ref />; this makes no sense.
  133. return $this->error( 'cite_error_ref_no_key' );
  134. }
  135. if( preg_match( '/^[0-9]+$/', $key ) ) {
  136. # Numeric names mess up the resulting id's, potentially produ-
  137. # cing duplicate id's in the XHTML. The Right Thing To Do
  138. # would be to mangle them, but it's not really high-priority
  139. # (and would produce weird id's anyway).
  140. return $this->error( 'cite_error_ref_numeric_key' );
  141. }
  142. if( preg_match(
  143. '/<ref\b[^<]*?>/',
  144. preg_replace( '#<([^ ]+?).*?>.*?</\\1 *>|<!--.*?-->#', '', $str )
  145. ) ) {
  146. # (bug 6199) This most likely implies that someone left off the
  147. # closing </ref> tag, which will cause the entire article to be
  148. # eaten up until the next <ref>. So we bail out early instead.
  149. # The fancy regex above first tries chopping out anything that
  150. # looks like a comment or SGML tag, which is a crude way to avoid
  151. # false alarms for <nowiki>, <pre>, etc.
  152. #
  153. # Possible improvement: print the warning, followed by the contents
  154. # of the <ref> tag. This way no part of the article will be eaten
  155. # even temporarily.
  156. return $this->error( 'cite_error_included_ref' );
  157. }
  158. # Split these into groups.
  159. if( $group === null ) {
  160. $group = $default_group;
  161. }
  162. if( is_string( $key ) or is_string( $str ) ) {
  163. # We don't care about the content: if the key exists, the ref
  164. # is presumptively valid. Either it stores a new ref, or re-
  165. # fers to an existing one. If it refers to a nonexistent ref,
  166. # we'll figure that out later. Likewise it's definitely valid
  167. # if there's any content, regardless of key.
  168. return $this->stack( $str, $key, $group );
  169. }
  170. # Not clear how we could get here, but something is probably
  171. # wrong with the types. Let's fail fast.
  172. $this->croak( 'cite_error_key_str_invalid', serialize( "$str; $key" ) );
  173. }
  174. /**
  175. * Parse the arguments to the <ref> tag
  176. *
  177. * @static
  178. *
  179. * @param array $argv The argument vector
  180. * @return mixed false on invalid input, a string on valid
  181. * input and null on no input
  182. */
  183. function refArg( $argv ) {
  184. global $wgAllowCiteGroups;
  185. $cnt = count( $argv );
  186. $group = null;
  187. $key = null;
  188. if ( $cnt > 2 )
  189. // There should only be one key and one group
  190. return false;
  191. else if ( $cnt >= 1 ) {
  192. if ( isset( $argv['name'] ) ) {
  193. // Key given.
  194. $key = Sanitizer::escapeId( $argv['name'], 'noninitial' );
  195. unset( $argv['name']);
  196. --$cnt;
  197. }
  198. if ( isset( $argv['group'] ) ){
  199. if (! $wgAllowCiteGroups ) return array(false); //remove when groups are fully tested.
  200. // Group given.
  201. $group = $argv['group'];
  202. unset( $argv['group']);
  203. --$cnt;
  204. }
  205. if ( $cnt == 0)
  206. return array ($key,$group);
  207. else
  208. // Invalid key
  209. return array(false,false);
  210. }
  211. else
  212. // No key
  213. return array(null,$group);
  214. }
  215. /**
  216. * Populate $this->mRefs based on input and arguments to <ref>
  217. *
  218. * @param string $str Input from the <ref> tag
  219. * @param mixed $key Argument to the <ref> tag as returned by $this->refArg()
  220. * @return string
  221. */
  222. function stack( $str, $key = null, $group ) {
  223. if (! isset($this->mRefs[$group]))
  224. $this->mRefs[$group]=array();
  225. if (! isset($this->mGroupCnt[$group]))
  226. $this->mGroupCnt[$group]=0;
  227. if ( $key === null ) {
  228. // No key
  229. //$this->mRefs[$group][] = $str;
  230. $this->mRefs[$group][] = array('count'=>-1, 'text'=>$str, 'key'=>++$this->mOutCnt);
  231. return $this->linkRef( $group, $this->mInCnt++ );
  232. } else if ( is_string( $key ) ) {
  233. // Valid key
  234. if ( ! isset( $this->mRefs[$group][$key] ) || ! is_array( $this->mRefs[$group][$key] ) ) {
  235. // First occurance
  236. $this->mRefs[$group][$key] = array(
  237. 'text' => $str,
  238. 'count' => 0,
  239. 'key' => ++$this->mOutCnt,
  240. 'number' => ++$this->mGroupCnt[$group]
  241. );
  242. $this->mInCnt++;
  243. return
  244. $this->linkRef(
  245. $group,
  246. $key,
  247. $this->mRefs[$group][$key]['key']."-".$this->mRefs[$group][$key]['count'],
  248. $this->mRefs[$group][$key]['number'],
  249. "-".$this->mRefs[$group][$key]['key']
  250. );
  251. } else {
  252. // We've been here before
  253. if ( $this->mRefs[$group][$key]['text'] === null && $str !== '' ) {
  254. // If no text found before, use this text
  255. $this->mRefs[$group][$key]['text'] = $str;
  256. };
  257. return
  258. $this->linkRef(
  259. $group,
  260. $key,
  261. $this->mRefs[$group][$key]['key']."-".++$this->mRefs[$group][$key]['count'],
  262. $this->mRefs[$group][$key]['number'],
  263. "-".$this->mRefs[$group][$key]['key']
  264. ); }
  265. }
  266. else
  267. $this->croak( 'cite_error_stack_invalid_input', serialize( array( $key, $str ) ) );
  268. }
  269. /**
  270. * Callback function for <references>
  271. *
  272. * @param string $str Input
  273. * @param array $argv Arguments
  274. * @return string
  275. */
  276. function references( $str, $argv, $parser ) {
  277. wfLoadExtensionMessages( 'Cite' );
  278. if ( $this->mInCite ) {
  279. if ( is_null( $str ) ) {
  280. return htmlspecialchars( "<references/>" );
  281. } else {
  282. return htmlspecialchars( "<references>$str</references>" );
  283. }
  284. } else {
  285. $this->mInCite = true;
  286. $ret = $this->guardedReferences( $str, $argv, $parser );
  287. $this->mInCite = false;
  288. return $ret;
  289. }
  290. }
  291. function guardedReferences( $str, $argv, $parser, $group = CITE_DEFAULT_GROUP ) {
  292. global $wgAllowCiteGroups;
  293. $this->mParser = $parser;
  294. if ( strval( $str ) !== '' )
  295. return $this->error( 'cite_error_references_invalid_input' );
  296. if ( isset( $argv['group'] ) and $wgAllowCiteGroups) {
  297. $group = $argv['group'];
  298. unset ($argv['group']);
  299. }
  300. if ( count( $argv ) && $wgAllowCiteGroups )
  301. return $this->error( 'cite_error_references_invalid_parameters_group' );
  302. elseif ( count( $argv ) )
  303. return $this->error( 'cite_error_references_invalid_parameters' );
  304. else
  305. return $this->referencesFormat($group);
  306. }
  307. /**
  308. * Make output to be returned from the references() function
  309. *
  310. * @return string XHTML ready for output
  311. */
  312. function referencesFormat($group) {
  313. if (( count( $this->mRefs ) == 0 ) or (empty( $this->mRefs[$group] ) ))
  314. return '';
  315. wfProfileIn( __METHOD__ );
  316. wfProfileIn( __METHOD__ .'-entries' );
  317. $ent = array();
  318. foreach ( $this->mRefs[$group] as $k => $v )
  319. $ent[] = $this->referencesFormatEntry( $k, $v );
  320. $prefix = wfMsgForContentNoTrans( 'cite_references_prefix' );
  321. $suffix = wfMsgForContentNoTrans( 'cite_references_suffix' );
  322. $content = implode( "\n", $ent );
  323. // Let's try to cache it.
  324. $parserInput = $prefix . $content . $suffix;
  325. global $wgMemc;
  326. $cacheKey = wfMemcKey( 'citeref', md5($parserInput), $this->mParser->Title()->getArticleID() );
  327. wfProfileOut( __METHOD__ .'-entries' );
  328. global $wgCiteCacheReferences;
  329. if ( $wgCiteCacheReferences ) {
  330. wfProfileIn( __METHOD__.'-cache-get' );
  331. $data = $wgMemc->get( $cacheKey );
  332. wfProfileOut( __METHOD__.'-cache-get' );
  333. }
  334. if ( empty($data) ) {
  335. wfProfileIn( __METHOD__ .'-parse' );
  336. // Live hack: parse() adds two newlines on WM, can't reproduce it locally -ævar
  337. $ret = rtrim( $this->parse( $parserInput ), "\n" );
  338. if ( $wgCiteCacheReferences ) {
  339. $serData = $this->mParser->serialiseHalfParsedText( $ret );
  340. $wgMemc->set( $cacheKey, $serData, 86400 );
  341. }
  342. wfProfileOut( __METHOD__ .'-parse' );
  343. } else {
  344. $ret = $this->mParser->unserialiseHalfParsedText( $data );
  345. }
  346. wfProfileOut( __METHOD__ );
  347. //done, clean up so we can reuse the group
  348. unset ($this->mRefs[$group]);
  349. unset($this->mGroupCnt[$group]);
  350. return $ret;
  351. }
  352. /**
  353. * Format a single entry for the referencesFormat() function
  354. *
  355. * @param string $key The key of the reference
  356. * @param mixed $val The value of the reference, string for anonymous
  357. * references, array for user-suppplied
  358. * @return string Wikitext
  359. */
  360. function referencesFormatEntry( $key, $val ) {
  361. // Anonymous reference
  362. if ( ! is_array( $val ) )
  363. return
  364. wfMsgForContentNoTrans(
  365. 'cite_references_link_one',
  366. $this->referencesKey( $key ),
  367. $this->refKey( $key ),
  368. $val
  369. );
  370. else if ($val['text']=='') return
  371. wfMsgForContentNoTrans(
  372. 'cite_references_link_one',
  373. $this->referencesKey( $key ),
  374. $this->refKey( $key, $val['count'] ),
  375. $this->error( 'cite_error_references_no_text', $key )
  376. );
  377. if ( $val['count'] < 0 )
  378. return
  379. wfMsgForContentNoTrans(
  380. 'cite_references_link_one',
  381. $this->referencesKey( $val['key'] ),
  382. #$this->refKey( $val['key'], $val['count'] ),
  383. $this->refKey( $val['key'] ),
  384. ( $val['text'] != '' ? $val['text'] : $this->error( 'cite_error_references_no_text', $key ) )
  385. );
  386. // Standalone named reference, I want to format this like an
  387. // anonymous reference because displaying "1. 1.1 Ref text" is
  388. // overkill and users frequently use named references when they
  389. // don't need them for convenience
  390. else if ( $val['count'] === 0 )
  391. return
  392. wfMsgForContentNoTrans(
  393. 'cite_references_link_one',
  394. $this->referencesKey( $key ."-" . $val['key'] ),
  395. #$this->refKey( $key, $val['count'] ),
  396. $this->refKey( $key, $val['key']."-".$val['count'] ),
  397. ( $val['text'] != '' ? $val['text'] : $this->error( 'cite_error_references_no_text', $key ) )
  398. );
  399. // Named references with >1 occurrences
  400. else {
  401. $links = array();
  402. //for group handling, we have an extra key here.
  403. for ( $i = 0; $i <= $val['count']; ++$i ) {
  404. $links[] = wfMsgForContentNoTrans(
  405. 'cite_references_link_many_format',
  406. $this->refKey( $key, $val['key']."-$i" ),
  407. $this->referencesFormatEntryNumericBacklinkLabel( $val['number'], $i, $val['count'] ),
  408. $this->referencesFormatEntryAlternateBacklinkLabel( $i )
  409. );
  410. }
  411. $list = $this->listToText( $links );
  412. return
  413. wfMsgForContentNoTrans( 'cite_references_link_many',
  414. $this->referencesKey( $key ."-" . $val['key'] ),
  415. $list,
  416. ( $val['text'] != '' ? $val['text'] : $this->error( 'cite_error_references_no_text', $key ) )
  417. );
  418. }
  419. }
  420. /**
  421. * Generate a numeric backlink given a base number and an
  422. * offset, e.g. $base = 1, $offset = 2; = 1.2
  423. * Since bug #5525, it correctly does 1.9 -> 1.10 as well as 1.099 -> 1.100
  424. *
  425. * @static
  426. *
  427. * @param int $base The base
  428. * @param int $offset The offset
  429. * @param int $max Maximum value expected.
  430. * @return string
  431. */
  432. function referencesFormatEntryNumericBacklinkLabel( $base, $offset, $max ) {
  433. global $wgContLang;
  434. $scope = strlen( $max );
  435. $ret = $wgContLang->formatNum(
  436. sprintf("%s.%0{$scope}s", $base, $offset)
  437. );
  438. return $ret;
  439. }
  440. /**
  441. * Generate a custom format backlink given an offset, e.g.
  442. * $offset = 2; = c if $this->mBacklinkLabels = array( 'a',
  443. * 'b', 'c', ...). Return an error if the offset > the # of
  444. * array items
  445. *
  446. * @param int $offset The offset
  447. *
  448. * @return string
  449. */
  450. function referencesFormatEntryAlternateBacklinkLabel( $offset ) {
  451. if ( !isset( $this->mBacklinkLabels ) ) {
  452. $this->genBacklinkLabels();
  453. }
  454. if ( isset( $this->mBacklinkLabels[$offset] ) ) {
  455. return $this->mBacklinkLabels[$offset];
  456. } else {
  457. // Feed me!
  458. return $this->error( 'cite_error_references_no_backlink_label' );
  459. }
  460. }
  461. /**
  462. * Return an id for use in wikitext output based on a key and
  463. * optionally the number of it, used in <references>, not <ref>
  464. * (since otherwise it would link to itself)
  465. *
  466. * @static
  467. *
  468. * @param string $key The key
  469. * @param int $num The number of the key
  470. * @return string A key for use in wikitext
  471. */
  472. function refKey( $key, $num = null ) {
  473. $prefix = wfMsgForContent( 'cite_reference_link_prefix' );
  474. $suffix = wfMsgForContent( 'cite_reference_link_suffix' );
  475. if ( isset( $num ) )
  476. $key = wfMsgForContentNoTrans( 'cite_reference_link_key_with_num', $key, $num );
  477. return $prefix . $key . $suffix;
  478. }
  479. /**
  480. * Return an id for use in wikitext output based on a key and
  481. * optionally the number of it, used in <ref>, not <references>
  482. * (since otherwise it would link to itself)
  483. *
  484. * @static
  485. *
  486. * @param string $key The key
  487. * @param int $num The number of the key
  488. * @return string A key for use in wikitext
  489. */
  490. function referencesKey( $key, $num = null ) {
  491. $prefix = wfMsgForContent( 'cite_references_link_prefix' );
  492. $suffix = wfMsgForContent( 'cite_references_link_suffix' );
  493. if ( isset( $num ) )
  494. $key = wfMsgForContentNoTrans( 'cite_reference_link_key_with_num', $key, $num );
  495. return $prefix . $key . $suffix;
  496. }
  497. /**
  498. * Generate a link (<sup ...) for the <ref> element from a key
  499. * and return XHTML ready for output
  500. *
  501. * @param string $key The key for the link
  502. * @param int $count The index of the key, used for distinguishing
  503. * multiple occurances of the same key
  504. * @param int $label The label to use for the link, I want to
  505. * use the same label for all occourances of
  506. * the same named reference.
  507. * @return string
  508. */
  509. function linkRef( $group, $key, $count = null, $label = null, $subkey = '' ) {
  510. global $wgContLang;
  511. return
  512. $this->parse(
  513. wfMsgForContentNoTrans(
  514. 'cite_reference_link',
  515. $this->refKey( $key, $count ),
  516. $this->referencesKey( $key . $subkey ),
  517. (($group == CITE_DEFAULT_GROUP)?'':"$group ").$wgContLang->formatNum( is_null( $label ) ? ++$this->mGroupCnt[$group] : $label )
  518. )
  519. );
  520. }
  521. /**
  522. * This does approximately the same thing as
  523. * Language::listToText() but due to this being used for a
  524. * slightly different purpose (people might not want , as the
  525. * first separator and not 'and' as the second, and this has to
  526. * use messages from the content language) I'm rolling my own.
  527. *
  528. * @static
  529. *
  530. * @param array $arr The array to format
  531. * @return string
  532. */
  533. function listToText( $arr ) {
  534. $cnt = count( $arr );
  535. $sep = wfMsgForContentNoTrans( 'cite_references_link_many_sep' );
  536. $and = wfMsgForContentNoTrans( 'cite_references_link_many_and' );
  537. if ( $cnt == 1 )
  538. // Enforce always returning a string
  539. return (string)$arr[0];
  540. else {
  541. $t = array_slice( $arr, 0, $cnt - 1 );
  542. return implode( $sep, $t ) . $and . $arr[$cnt - 1];
  543. }
  544. }
  545. /**
  546. * Parse a given fragment and fix up Tidy's trail of blood on
  547. * it...
  548. *
  549. * @param string $in The text to parse
  550. * @return string The parsed text
  551. */
  552. function parse( $in ) {
  553. if ( method_exists( $this->mParser, 'recursiveTagParse' ) ) {
  554. // New fast method
  555. return $this->mParser->recursiveTagParse( $in );
  556. } else {
  557. // Old method
  558. $ret = $this->mParser->parse(
  559. $in,
  560. $this->mParser->mTitle,
  561. $this->mParser->mOptions,
  562. // Avoid whitespace buildup
  563. false,
  564. // Important, otherwise $this->clearState()
  565. // would get run every time <ref> or
  566. // <references> is called, fucking the whole
  567. // thing up.
  568. false
  569. );
  570. $text = $ret->getText();
  571. return $this->fixTidy( $text );
  572. }
  573. }
  574. /**
  575. * Tidy treats all input as a block, it will e.g. wrap most
  576. * input in <p> if it isn't already, fix that and return the fixed text
  577. *
  578. * @static
  579. *
  580. * @param string $text The text to fix
  581. * @return string The fixed text
  582. */
  583. function fixTidy( $text ) {
  584. global $wgUseTidy;
  585. if ( ! $wgUseTidy )
  586. return $text;
  587. else {
  588. $text = preg_replace( '~^<p>\s*~', '', $text );
  589. $text = preg_replace( '~\s*</p>\s*~', '', $text );
  590. $text = preg_replace( '~\n$~', '', $text );
  591. return $text;
  592. }
  593. }
  594. /**
  595. * Generate the labels to pass to the
  596. * 'cite_references_link_many_format' message, the format is an
  597. * arbitary number of tokens separated by [\t\n ]
  598. */
  599. function genBacklinkLabels() {
  600. wfProfileIn( __METHOD__ );
  601. $text = wfMsgForContentNoTrans( 'cite_references_link_many_format_backlink_labels' );
  602. $this->mBacklinkLabels = preg_split( '#[\n\t ]#', $text );
  603. wfProfileOut( __METHOD__ );
  604. }
  605. /**
  606. * Gets run when Parser::clearState() gets run, since we don't
  607. * want the counts to transcend pages and other instances
  608. */
  609. function clearState() {
  610. # Don't clear state when we're in the middle of parsing
  611. # a <ref> tag
  612. if($this->mInCite)
  613. return true;
  614. $this->mGroupCnt = array();
  615. $this->mOutCnt = -1;
  616. $this->mInCnt = 0;
  617. $this->mRefs = array();
  618. return true;
  619. }
  620. /**
  621. * Called at the end of page processing to append an error if refs were
  622. * used without a references tag.
  623. */
  624. function checkRefsNoReferences(&$parser, &$text){
  625. if ( $parser->getOptions()->getIsSectionPreview() ) return true;
  626. foreach ( $this->mRefs as $group => $refs ) {
  627. if ( count( $refs ) == 0 ) continue;
  628. $text .= "\n<br />";
  629. if ( $group == CITE_DEFAULT_GROUP ) {
  630. $text .= $this->error( 'cite_error_refs_without_references' );
  631. } else {
  632. $text .= $this->error( 'cite_error_group_refs_without_references', htmlspecialchars( $group ) );
  633. }
  634. }
  635. return true;
  636. }
  637. /**
  638. * Initialize the parser hooks
  639. */
  640. function setHooks() {
  641. global $wgParser, $wgHooks;
  642. $wgParser->setHook( 'ref' , array( &$this, 'ref' ) );
  643. $wgParser->setHook( 'references' , array( &$this, 'references' ) );
  644. $wgHooks['ParserClearState'][] = array( &$this, 'clearState' );
  645. $wgHooks['ParserBeforeTidy'][] = array( &$this, 'checkRefsNoReferences' );
  646. }
  647. /**
  648. * Return an error message based on an error ID
  649. *
  650. * @param string $key Message name for the error
  651. * @param string $param Parameter to pass to the message
  652. * @return string XHTML ready for output
  653. */
  654. function error( $key, $param=null ) {
  655. # We rely on the fact that PHP is okay with passing unused argu-
  656. # ments to functions. If $1 is not used in the message, wfMsg will
  657. # just ignore the extra parameter.
  658. return
  659. $this->parse(
  660. '<strong class="error">' .
  661. wfMsgNoTrans( 'cite_error', wfMsgNoTrans( $key, $param ) ) .
  662. '</strong>'
  663. );
  664. }
  665. /**
  666. * Die with a backtrace if something happens in the code which
  667. * shouldn't have
  668. *
  669. * @param int $error ID for the error
  670. * @param string $data Serialized error data
  671. */
  672. function croak( $error, $data ) {
  673. wfDebugDieBacktrace( wfMsgForContent( 'cite_croak', $this->error( $error ), $data ) );
  674. }
  675. /**#@-*/
  676. }
  677. ?>