[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/extensions/Cite/ -> Cite_body.php (source)

   1  <?php
   2  
   3  /**#@+
   4   * A parser extension that adds two tags, <ref> and <references> for adding
   5   * citations to pages
   6   *
   7   * @ingroup Extensions
   8   *
   9   * @link http://www.mediawiki.org/wiki/Extension:Cite/Cite.php Documentation
  10   * @link http://www.w3.org/TR/html4/struct/text.html#edef-CITE <cite> definition in HTML
  11   * @link http://www.w3.org/TR/2005/WD-xhtml2-20050527/mod-text.html#edef_text_cite <cite> definition in XHTML 2.0
  12   *
  13   * @bug 4579
  14   *
  15   * @author Ævar Arnfjörð Bjarmason <[email protected]>
  16   * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
  17   * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
  18   */
  19  
  20  class Cite {
  21      /**#@+
  22       * @access private
  23       */
  24  
  25      /**
  26       * Datastructure representing <ref> input, in the format of:
  27       * <code>
  28       * array(
  29       *     'user supplied' => array(
  30       *        'text' => 'user supplied reference & key',
  31       *        'count' => 1, // occurs twice
  32       *         'number' => 1, // The first reference, we want
  33       *                        // all occourances of it to
  34       *                        // use the same number
  35       *    ),
  36       *    0 => 'Anonymous reference',
  37       *    1 => 'Another anonymous reference',
  38       *    'some key' => array(
  39       *        'text' => 'this one occurs once'
  40       *        'count' => 0,
  41       *         'number' => 4
  42       *    ),
  43       *    3 => 'more stuff'
  44       * );
  45       * </code>
  46       *
  47       * This works because:
  48       * * PHP's datastructures are guaranteed to be returned in the
  49       *   order that things are inserted into them (unless you mess
  50       *   with that)
  51       * * User supplied keys can't be integers, therefore avoiding
  52       *   conflict with anonymous keys
  53       *
  54       * @var array
  55       **/
  56      public $mRefs = array();
  57  
  58      /**
  59       * Count for user displayed output (ref[1], ref[2], ...)
  60       *
  61       * @var int
  62       */
  63      public $mOutCnt = 0;
  64      public $mGroupCnt = array();
  65  
  66      /**
  67       * Counter to track the total number of (useful) calls to either the
  68       * ref or references tag hook
  69       */
  70      public $mCallCnt = 0;
  71  
  72      /**
  73       * The backlinks, in order, to pass as $3 to
  74       * 'cite_references_link_many_format', defined in
  75       * 'cite_references_link_many_format_backlink_labels
  76       *
  77       * @var array
  78       */
  79      public $mBacklinkLabels;
  80  
  81      /**
  82       * The links to use per group, in order.
  83       *
  84       * @var array
  85       */
  86      public $mLinkLabels = array();
  87  
  88      /**
  89       * @var Parser
  90       */
  91      public $mParser;
  92  
  93      /**
  94       * True when the ParserAfterParse hook has been called.
  95       * Used to avoid doing anything in ParserBeforeTidy.
  96       *
  97       * @var boolean
  98       */
  99      public $mHaveAfterParse = false;
 100  
 101      /**
 102       * True when a <ref> tag is being processed.
 103       * Used to avoid infinite recursion
 104       *
 105       * @var boolean
 106       */
 107      public $mInCite = false;
 108  
 109      /**
 110       * True when a <references> tag is being processed.
 111       * Used to detect the use of <references> to define refs
 112       *
 113       * @var boolean
 114       */
 115      public $mInReferences = false;
 116  
 117      /**
 118       * Error stack used when defining refs in <references>
 119       *
 120       * @var array
 121       */
 122      public $mReferencesErrors = array();
 123  
 124      /**
 125       * Group used when in <references> block
 126       *
 127       * @var string
 128       */
 129      public $mReferencesGroup = '';
 130  
 131      /**
 132       * <ref> call stack
 133       * Used to cleanup out of sequence ref calls created by #tag
 134       * See description of function rollbackRef.
 135       *
 136       * @var array
 137       */
 138      public $mRefCallStack = array();
 139  
 140      /**
 141       * Did we install us into $wgHooks yet?
 142       * @var Boolean
 143       */
 144      static protected $hooksInstalled = false;
 145  
 146      /**#@+ @access private */
 147  
 148      /**
 149       * Callback function for <ref>
 150       *
 151       * @param $str string Input
 152       * @param $argv array Arguments
 153       * @param $parser Parser
 154       * @param $frame PPFrame
 155       *
 156       * @return string
 157       */
 158  	function ref( $str, $argv, $parser, $frame ) {
 159          if ( $this->mInCite ) {
 160              return htmlspecialchars( "<ref>$str</ref>" );
 161          } else {
 162              $this->mCallCnt++;
 163              $this->mInCite = true;
 164              $ret = $this->guardedRef( $str, $argv, $parser );
 165              $this->mInCite = false;
 166              $parserOutput = $parser->getOutput();
 167              $parserOutput->addModules( 'ext.cite' );
 168              $parserOutput->addModuleStyles( 'ext.rtlcite' );
 169              if ( is_callable( array( $frame, 'setVolatile' ) ) ) {
 170                  $frame->setVolatile();
 171              }
 172              return $ret;
 173          }
 174      }
 175  
 176      /**
 177       * @param $str string Input
 178       * @param $argv array Arguments
 179       * @param $parser Parser
 180       * @param $default_group string
 181       * @return string
 182       */
 183  	function guardedRef( $str, $argv, $parser, $default_group = CITE_DEFAULT_GROUP ) {
 184          $this->mParser = $parser;
 185  
 186          # The key here is the "name" attribute.
 187          list( $key, $group, $follow ) = $this->refArg( $argv );
 188  
 189          # Split these into groups.
 190          if ( $group === null ) {
 191              if ( $this->mInReferences ) {
 192                  $group = $this->mReferencesGroup;
 193              } else {
 194                  $group = $default_group;
 195              }
 196          }
 197  
 198          # This section deals with constructions of the form
 199          #
 200          # <references>
 201          # <ref name="foo"> BAR </ref>
 202          # </references>
 203          #
 204          if ( $this->mInReferences ) {
 205              if ( $group != $this->mReferencesGroup ) {
 206                  # <ref> and <references> have conflicting group attributes.
 207                  $this->mReferencesErrors[] =
 208                      $this->error( 'cite_error_references_group_mismatch', htmlspecialchars( $group ) );
 209              } elseif ( $str !== '' ) {
 210                  if ( !isset( $this->mRefs[$group] ) ) {
 211                      # Called with group attribute not defined in text.
 212                      $this->mReferencesErrors[] =
 213                          $this->error( 'cite_error_references_missing_group', htmlspecialchars( $group ) );
 214                  } elseif ( $key === null || $key === '' ) {
 215                      # <ref> calls inside <references> must be named
 216                      $this->mReferencesErrors[] =
 217                          $this->error( 'cite_error_references_no_key' );
 218                  } elseif ( !isset( $this->mRefs[$group][$key] ) ) {
 219                      # Called with name attribute not defined in text.
 220                      $this->mReferencesErrors[] =
 221                          $this->error( 'cite_error_references_missing_key', $key );
 222                  } else {
 223                      # Assign the text to corresponding ref
 224                      $this->mRefs[$group][$key]['text'] = $str;
 225                  }
 226              } else {
 227                  # <ref> called in <references> has no content.
 228                  $this->mReferencesErrors[] =
 229                      $this->error( 'cite_error_empty_references_define', $key );
 230              }
 231              return '';
 232          }
 233  
 234          if ( $str === '' ) {
 235              # <ref ...></ref>.  This construct is  invalid if
 236              # it's a contentful ref, but OK if it's a named duplicate and should
 237              # be equivalent <ref ... />, for compatability with #tag.
 238              if ( $key == false ) {
 239                  $this->mRefCallStack[] = false;
 240                  return $this->error( 'cite_error_ref_no_input' );
 241              } else {
 242                  $str = null;
 243              }
 244          }
 245  
 246          if ( $key === false ) {
 247              # TODO: Comment this case; what does this condition mean?
 248              $this->mRefCallStack[] = false;
 249              return $this->error( 'cite_error_ref_too_many_keys' );
 250          }
 251  
 252          if ( $str === null && $key === null ) {
 253              # Something like <ref />; this makes no sense.
 254              $this->mRefCallStack[] = false;
 255              return $this->error( 'cite_error_ref_no_key' );
 256          }
 257  
 258          if ( preg_match( '/^[0-9]+$/', $key ) || preg_match( '/^[0-9]+$/', $follow ) ) {
 259              # Numeric names mess up the resulting id's, potentially produ-
 260              # cing duplicate id's in the XHTML.  The Right Thing To Do
 261              # would be to mangle them, but it's not really high-priority
 262              # (and would produce weird id's anyway).
 263  
 264              $this->mRefCallStack[] = false;
 265              return $this->error( 'cite_error_ref_numeric_key' );
 266          }
 267  
 268          if ( preg_match(
 269              '/<ref\b[^<]*?>/',
 270              preg_replace( '#<([^ ]+?).*?>.*?</\\1 *>|<!--.*?-->#', '', $str )
 271          ) ) {
 272              # (bug 6199) This most likely implies that someone left off the
 273              # closing </ref> tag, which will cause the entire article to be
 274              # eaten up until the next <ref>.  So we bail out early instead.
 275              # The fancy regex above first tries chopping out anything that
 276              # looks like a comment or SGML tag, which is a crude way to avoid
 277              # false alarms for <nowiki>, <pre>, etc.
 278              #
 279              # Possible improvement: print the warning, followed by the contents
 280              # of the <ref> tag.  This way no part of the article will be eaten
 281              # even temporarily.
 282  
 283              $this->mRefCallStack[] = false;
 284              return $this->error( 'cite_error_included_ref' );
 285          }
 286  
 287          if ( is_string( $key ) || is_string( $str ) ) {
 288              # We don't care about the content: if the key exists, the ref
 289              # is presumptively valid.  Either it stores a new ref, or re-
 290              # fers to an existing one.  If it refers to a nonexistent ref,
 291              # we'll figure that out later.  Likewise it's definitely valid
 292              # if there's any content, regardless of key.
 293  
 294              return $this->stack( $str, $key, $group, $follow, $argv );
 295          }
 296  
 297          # Not clear how we could get here, but something is probably
 298          # wrong with the types.  Let's fail fast.
 299          throw new MWException( 'Invalid $str and/or $key: ' . serialize( array( $str, $key ) ) );
 300      }
 301  
 302      /**
 303       * Parse the arguments to the <ref> tag
 304       *
 305       *  "name" : Key of the reference.
 306       *  "group" : Group to which it belongs. Needs to be passed to <references /> too.
 307       *  "follow" : If the current reference is the continuation of another, key of that reference.
 308       *
 309       *
 310       * @param $argv array The argument vector
 311       * @return mixed false on invalid input, a string on valid
 312       *               input and null on no input
 313       */
 314  	function refArg( $argv ) {
 315          global $wgAllowCiteGroups;
 316          $cnt = count( $argv );
 317          $group = null;
 318          $key = null;
 319          $follow = null;
 320  
 321          if ( $cnt > 2 ) {
 322              // There should only be one key or follow parameter, and one group parameter
 323              // FIXME : this looks inconsistent, it should probably return a tuple
 324              return false;
 325          } elseif ( $cnt >= 1 ) {
 326              if ( isset( $argv['name'] ) && isset( $argv['follow'] ) ) {
 327                  return array( false, false, false );
 328              }
 329              if ( isset( $argv['name'] ) ) {
 330                  // Key given.
 331                  $key = Sanitizer::escapeId( $argv['name'], 'noninitial' );
 332                  unset( $argv['name'] );
 333                  --$cnt;
 334              }
 335              if ( isset( $argv['follow'] ) ) {
 336                  // Follow given.
 337                  $follow = Sanitizer::escapeId( $argv['follow'], 'noninitial' );
 338                  unset( $argv['follow'] );
 339                  --$cnt;
 340              }
 341              if ( isset( $argv['group'] ) ) {
 342                  if ( !$wgAllowCiteGroups ) {
 343                      // remove when groups are fully tested.
 344                      return array( false );
 345                  }
 346                  // Group given.
 347                  $group = $argv['group'];
 348                  unset( $argv['group'] );
 349                  --$cnt;
 350              }
 351  
 352              if ( $cnt == 0 ) {
 353                  return array ( $key, $group, $follow );
 354              } else {
 355                  // Invalid key
 356                  return array( false, false, false );
 357              }
 358          } else {
 359              // No key
 360              return array( null, $group, false );
 361          }
 362      }
 363  
 364      /**
 365       * Populate $this->mRefs based on input and arguments to <ref>
 366       *
 367       * @param $str string Input from the <ref> tag
 368       * @param $key mixed Argument to the <ref> tag as returned by $this->refArg()
 369       * @param $group
 370       * @param $follow
 371       * @param $call
 372       *
 373       * @return string
 374       */
 375  	function stack( $str, $key = null, $group, $follow, $call ) {
 376          if ( !isset( $this->mRefs[$group] ) ) {
 377              $this->mRefs[$group] = array();
 378          }
 379          if ( !isset( $this->mGroupCnt[$group] ) ) {
 380              $this->mGroupCnt[$group] = 0;
 381          }
 382  
 383          if ( $follow != null ) {
 384              if ( isset( $this->mRefs[$group][$follow] ) && is_array( $this->mRefs[$group][$follow] ) ) {
 385                  // add text to the note that is being followed
 386                  $this->mRefs[$group][$follow]['text'] = $this->mRefs[$group][$follow]['text'] . ' ' . $str;
 387              } else {
 388                  // insert part of note at the beginning of the group
 389                  for ( $k = 0 ; $k < count( $this->mRefs[$group] ) ; $k++ ) {
 390                      if ( $this->mRefs[$group][$k]['follow'] == null ) {
 391                          break;
 392                      }
 393                  }
 394                  array_splice( $this->mRefs[$group], $k, 0,
 395                             array( array( 'count' => - 1,
 396                                'text' => $str,
 397                                'key' => ++$this->mOutCnt ,
 398                                'follow' => $follow ) ) );
 399                  array_splice( $this->mRefCallStack, $k, 0,
 400                             array( array( 'new', $call, $str, $key, $group, $this->mOutCnt ) ) );
 401              }
 402              // return an empty string : this is not a reference
 403              return '';
 404          }
 405          if ( $key === null ) {
 406              // No key
 407              // $this->mRefs[$group][] = $str;
 408              $this->mRefs[$group][] = array( 'count' => - 1, 'text' => $str, 'key' => ++$this->mOutCnt );
 409              $this->mRefCallStack[] = array( 'new', $call, $str, $key, $group, $this->mOutCnt );
 410  
 411              return $this->linkRef( $group, $this->mOutCnt );
 412          } elseif ( is_string( $key ) ) {
 413              // Valid key
 414              if ( !isset( $this->mRefs[$group][$key] ) || !is_array( $this->mRefs[$group][$key] ) ) {
 415                  // First occurrence
 416                  $this->mRefs[$group][$key] = array(
 417                      'text' => $str,
 418                      'count' => 0,
 419                      'key' => ++$this->mOutCnt,
 420                      'number' => ++$this->mGroupCnt[$group]
 421                  );
 422                  $this->mRefCallStack[] = array( 'new', $call, $str, $key, $group, $this->mOutCnt );
 423  
 424                  return
 425                      $this->linkRef(
 426                          $group,
 427                          $key,
 428                          $this->mRefs[$group][$key]['key'] . "-" . $this->mRefs[$group][$key]['count'],
 429                          $this->mRefs[$group][$key]['number'],
 430                          "-" . $this->mRefs[$group][$key]['key']
 431                      );
 432              } else {
 433                  // We've been here before
 434                  if ( $this->mRefs[$group][$key]['text'] === null && $str !== '' ) {
 435                      // If no text found before, use this text
 436                      $this->mRefs[$group][$key]['text'] = $str;
 437                      $this->mRefCallStack[] = array( 'assign', $call, $str, $key, $group,
 438                          $this->mRefs[$group][$key]['key'] );
 439                  } else {
 440                      $this->mRefCallStack[] = array( 'increment', $call, $str, $key, $group,
 441                          $this->mRefs[$group][$key]['key'] );
 442                  }
 443                  return
 444                      $this->linkRef(
 445                          $group,
 446                          $key,
 447                          $this->mRefs[$group][$key]['key'] . "-" . ++$this->mRefs[$group][$key]['count'],
 448                          $this->mRefs[$group][$key]['number'],
 449                          "-" . $this->mRefs[$group][$key]['key']
 450                      );
 451              }
 452          } else {
 453              throw new MWException( 'Invalid stack key: ' . serialize( $key ) );
 454          }
 455      }
 456  
 457      /**
 458       * Partially undoes the effect of calls to stack()
 459       *
 460       * Called by guardedReferences()
 461       *
 462       * The option to define <ref> within <references> makes the
 463       * behavior of <ref> context dependent.  This is normally fine
 464       * but certain operations (especially #tag) lead to out-of-order
 465       * parser evaluation with the <ref> tags being processed before
 466       * their containing <reference> element is read.  This leads to
 467       * stack corruption that this function works to fix.
 468       *
 469       * This function is not a total rollback since some internal
 470       * counters remain incremented.  Doing so prevents accidentally
 471       * corrupting certain links.
 472       *
 473       * @param $type
 474       * @param $key
 475       * @param $group
 476       * @param $index
 477       */
 478  	function rollbackRef( $type, $key, $group, $index ) {
 479          if ( !isset( $this->mRefs[$group] ) ) {
 480              return;
 481          }
 482  
 483          if ( $key === null ) {
 484              foreach ( $this->mRefs[$group] as $k => $v ) {
 485                  if ( $this->mRefs[$group][$k]['key'] === $index ) {
 486                      $key = $k;
 487                      break;
 488                  }
 489              }
 490          }
 491  
 492          # Sanity checks that specified element exists.
 493          if ( $key === null ) {
 494              return;
 495          }
 496          if ( !isset( $this->mRefs[$group][$key] ) ) {
 497              return;
 498          }
 499          if ( $this->mRefs[$group][$key]['key'] != $index ) {
 500              return;
 501          }
 502  
 503          switch ( $type ) {
 504          case 'new':
 505              # Rollback the addition of new elements to the stack.
 506              unset( $this->mRefs[$group][$key] );
 507              if ( count( $this->mRefs[$group] ) == 0 ) {
 508                  unset( $this->mRefs[$group] );
 509                  unset( $this->mGroupCnt[$group] );
 510              }
 511              break;
 512          case 'assign':
 513              # Rollback assignment of text to pre-existing elements.
 514              $this->mRefs[$group][$key]['text'] = null;
 515              # continue without break
 516          case 'increment':
 517              # Rollback increase in named ref occurrences.
 518              $this->mRefs[$group][$key]['count']--;
 519              break;
 520          }
 521      }
 522  
 523      /**
 524       * Callback function for <references>
 525       *
 526       * @param $str string Input
 527       * @param $argv array Arguments
 528       * @param $parser Parser
 529       * @param $frame PPFrame
 530       *
 531       * @return string
 532       */
 533  	function references( $str, $argv, $parser, $frame ) {
 534          if ( $this->mInCite || $this->mInReferences ) {
 535              if ( is_null( $str ) ) {
 536                  return htmlspecialchars( "<references/>" );
 537              } else {
 538                  return htmlspecialchars( "<references>$str</references>" );
 539              }
 540          } else {
 541              $this->mCallCnt++;
 542              $this->mInReferences = true;
 543              $ret = $this->guardedReferences( $str, $argv, $parser );
 544              $this->mInReferences = false;
 545              if ( is_callable( array( $frame, 'setVolatile' ) ) ) {
 546                  $frame->setVolatile();
 547              }
 548              return $ret;
 549          }
 550      }
 551  
 552      /**
 553       * @param $str string
 554       * @param $argv array
 555       * @param $parser Parser
 556       * @param $group string
 557       * @return string
 558       */
 559  	function guardedReferences( $str, $argv, $parser, $group = CITE_DEFAULT_GROUP ) {
 560          global $wgAllowCiteGroups;
 561  
 562          $this->mParser = $parser;
 563  
 564          if ( isset( $argv['group'] ) && $wgAllowCiteGroups ) {
 565              $group = $argv['group'];
 566              unset ( $argv['group'] );
 567          }
 568  
 569          if ( strval( $str ) !== '' ) {
 570              $this->mReferencesGroup = $group;
 571  
 572              # Detect whether we were sent already rendered <ref>s
 573              # Mostly a side effect of using #tag to call references
 574              $count = substr_count( $str, $parser->uniqPrefix() . "-ref-" );
 575              for ( $i = 1; $i <= $count; $i++ ) {
 576                  if ( count( $this->mRefCallStack ) < 1 ) {
 577                      break;
 578                  }
 579  
 580                  # The following assumes that the parsed <ref>s sent within
 581                  # the <references> block were the most recent calls to
 582                  # <ref>.  This assumption is true for all known use cases,
 583                  # but not strictly enforced by the parser.  It is possible
 584                  # that some unusual combination of #tag, <references> and
 585                  # conditional parser functions could be created that would
 586                  # lead to malformed references here.
 587                  $call = array_pop( $this->mRefCallStack );
 588                  if ( $call !== false ) {
 589                      list( $type, $ref_argv, $ref_str,
 590                          $ref_key, $ref_group, $ref_index ) = $call;
 591  
 592                      # Undo effects of calling <ref> while unaware of containing <references>
 593                      $this->rollbackRef( $type, $ref_key, $ref_group, $ref_index );
 594  
 595                      # Rerun <ref> call now that mInReferences is set.
 596                      $this->guardedRef( $ref_str, $ref_argv, $parser );
 597                  }
 598              }
 599  
 600              # Parse $str to process any unparsed <ref> tags.
 601              $parser->recursiveTagParse( $str );
 602  
 603              # Reset call stack
 604              $this->mRefCallStack = array();
 605          }
 606  
 607          if ( count( $argv ) && $wgAllowCiteGroups ) {
 608              return $this->error( 'cite_error_references_invalid_parameters_group' );
 609          } elseif ( count( $argv ) ) {
 610              return $this->error( 'cite_error_references_invalid_parameters' );
 611          } else {
 612              $s = $this->referencesFormat( $group );
 613              if ( $parser->getOptions()->getIsSectionPreview() ) {
 614                  return $s;
 615              }
 616  
 617              # Append errors generated while processing <references>
 618              if ( count( $this->mReferencesErrors ) > 0 ) {
 619                  $s .= "\n" . implode( "<br />\n", $this->mReferencesErrors );
 620                  $this->mReferencesErrors = array();
 621              }
 622              return $s;
 623          }
 624      }
 625  
 626      /**
 627       * Make output to be returned from the references() function
 628       *
 629       * @param $group
 630       *
 631       * @return string XHTML ready for output
 632       */
 633  	function referencesFormat( $group ) {
 634          if ( ( count( $this->mRefs ) == 0 ) || ( empty( $this->mRefs[$group] ) ) ) {
 635              return '';
 636          }
 637  
 638          wfProfileIn( __METHOD__ );
 639          wfProfileIn( __METHOD__ . '-entries' );
 640          $ent = array();
 641          foreach ( $this->mRefs[$group] as $k => $v ) {
 642              $ent[] = $this->referencesFormatEntry( $k, $v );
 643          }
 644  
 645          $prefix = wfMessage( 'cite_references_prefix' )->inContentLanguage()->plain();
 646          $suffix = wfMessage( 'cite_references_suffix' )->inContentLanguage()->plain();
 647          $content = implode( "\n", $ent );
 648  
 649          // Prepare the parser input. We add new lines between the pieces to avoid a confused tidy (bug 13073)
 650          $parserInput = $prefix . "\n" . $content . "\n" . $suffix;
 651  
 652          // Let's try to cache it.
 653          global $wgMemc;
 654          $cacheKey = wfMemcKey( 'citeref', md5( $parserInput ), $this->mParser->Title()->getArticleID() );
 655  
 656          wfProfileOut( __METHOD__ . '-entries' );
 657  
 658          global $wgCiteCacheReferences;
 659          $data = false;
 660          if ( $wgCiteCacheReferences ) {
 661              wfProfileIn( __METHOD__ . '-cache-get' );
 662              $data = $wgMemc->get( $cacheKey );
 663              wfProfileOut( __METHOD__ . '-cache-get' );
 664          }
 665  
 666          if ( !$data || !$this->mParser->isValidHalfParsedText( $data ) ) {
 667              wfProfileIn( __METHOD__ . '-parse' );
 668  
 669              // Live hack: parse() adds two newlines on WM, can't reproduce it locally -ævar
 670              $ret = rtrim( $this->mParser->recursiveTagParse( $parserInput ), "\n" );
 671  
 672              if ( $wgCiteCacheReferences ) {
 673                  $serData = $this->mParser->serializeHalfParsedText( $ret );
 674                  $wgMemc->set( $cacheKey, $serData, 86400 );
 675              }
 676  
 677              wfProfileOut( __METHOD__ . '-parse' );
 678          } else {
 679              $ret = $this->mParser->unserializeHalfParsedText( $data );
 680          }
 681  
 682          wfProfileOut( __METHOD__ );
 683  
 684          // done, clean up so we can reuse the group
 685          unset( $this->mRefs[$group] );
 686          unset( $this->mGroupCnt[$group] );
 687  
 688          return $ret;
 689      }
 690  
 691      /**
 692       * Format a single entry for the referencesFormat() function
 693       *
 694       * @param string $key The key of the reference
 695       * @param mixed $val The value of the reference, string for anonymous
 696       *                   references, array for user-suppplied
 697       * @return string Wikitext
 698       */
 699  	function referencesFormatEntry( $key, $val ) {
 700          // Anonymous reference
 701          if ( !is_array( $val ) ) {
 702              return wfMessage(
 703                      'cite_references_link_one',
 704                      $this->referencesKey( $key ),
 705                      $this->refKey( $key ),
 706                      $this->referenceText( $key, $val )
 707                  )->inContentLanguage()->plain();
 708          }
 709          $text = $this->referenceText( $key, $val['text'] );
 710          if ( isset( $val['follow'] ) ) {
 711              return wfMessage(
 712                      'cite_references_no_link',
 713                      $this->referencesKey( $val['follow'] ),
 714                      $text
 715                  )->inContentLanguage()->plain();
 716          } elseif ( $val['text'] == '' ) {
 717              return wfMessage(
 718                          'cite_references_link_one',
 719                          $this->referencesKey( $key ),
 720                          $this->refKey( $key, $val['count'] ),
 721                          $text
 722                      )->inContentLanguage()->plain();
 723          }
 724  
 725          if ( $val['count'] < 0 ) {
 726              return wfMessage(
 727                      'cite_references_link_one',
 728                      $this->referencesKey( $val['key'] ),
 729                      # $this->refKey( $val['key'], $val['count'] ),
 730                      $this->refKey( $val['key'] ),
 731                      $text
 732                  )->inContentLanguage()->plain();
 733              // Standalone named reference, I want to format this like an
 734              // anonymous reference because displaying "1. 1.1 Ref text" is
 735              // overkill and users frequently use named references when they
 736              // don't need them for convenience
 737          } elseif ( $val['count'] === 0 ) {
 738              return wfMessage(
 739                      'cite_references_link_one',
 740                      $this->referencesKey( $key . "-" . $val['key'] ),
 741                      # $this->refKey( $key, $val['count'] ),
 742                      $this->refKey( $key, $val['key'] . "-" . $val['count'] ),
 743                      $text
 744                  )->inContentLanguage()->plain();
 745          // Named references with >1 occurrences
 746          } else {
 747              $links = array();
 748              // for group handling, we have an extra key here.
 749              for ( $i = 0; $i <= $val['count']; ++$i ) {
 750                  $links[] = wfMessage(
 751                          'cite_references_link_many_format',
 752                          $this->refKey( $key, $val['key'] . "-$i" ),
 753                          $this->referencesFormatEntryNumericBacklinkLabel( $val['number'], $i, $val['count'] ),
 754                          $this->referencesFormatEntryAlternateBacklinkLabel( $i )
 755                  )->inContentLanguage()->plain();
 756              }
 757  
 758              $list = $this->listToText( $links );
 759  
 760              return wfMessage( 'cite_references_link_many',
 761                      $this->referencesKey( $key . "-" . $val['key'] ),
 762                      $list,
 763                      $text
 764                  )->inContentLanguage()->plain();
 765          }
 766      }
 767  
 768      /**
 769       * Returns formatted reference text
 770       * @param String $key
 771       * @param String $text
 772       * @return String
 773       */
 774  	function referenceText( $key, $text ) {
 775          if ( $text == '' ) {
 776              return $this->error( 'cite_error_references_no_text', $key, 'noparse' );
 777          }
 778          return '<span class="reference-text">' . rtrim( $text, "\n" ) . "</span>\n";
 779      }
 780  
 781      /**
 782       * Generate a numeric backlink given a base number and an
 783       * offset, e.g. $base = 1, $offset = 2; = 1.2
 784       * Since bug #5525, it correctly does 1.9 -> 1.10 as well as 1.099 -> 1.100
 785       *
 786       * @static
 787       *
 788       * @param int $base The base
 789       * @param int $offset The offset
 790       * @param int $max Maximum value expected.
 791       * @return string
 792       */
 793  	function referencesFormatEntryNumericBacklinkLabel( $base, $offset, $max ) {
 794          global $wgContLang;
 795          $scope = strlen( $max );
 796          $ret = $wgContLang->formatNum(
 797              sprintf( "%s.%0{$scope}s", $base, $offset )
 798          );
 799          return $ret;
 800      }
 801  
 802      /**
 803       * Generate a custom format backlink given an offset, e.g.
 804       * $offset = 2; = c if $this->mBacklinkLabels = array( 'a',
 805       * 'b', 'c', ...). Return an error if the offset > the # of
 806       * array items
 807       *
 808       * @param int $offset The offset
 809       *
 810       * @return string
 811       */
 812  	function referencesFormatEntryAlternateBacklinkLabel( $offset ) {
 813          if ( !isset( $this->mBacklinkLabels ) ) {
 814              $this->genBacklinkLabels();
 815          }
 816          if ( isset( $this->mBacklinkLabels[$offset] ) ) {
 817              return $this->mBacklinkLabels[$offset];
 818          } else {
 819              // Feed me!
 820              return $this->error( 'cite_error_references_no_backlink_label', null, 'noparse' );
 821          }
 822      }
 823  
 824      /**
 825       * Generate a custom format link for a group given an offset, e.g.
 826       * the second <ref group="foo"> is b if $this->mLinkLabels["foo"] =
 827       * array( 'a', 'b', 'c', ...).
 828       * Return an error if the offset > the # of array items
 829       *
 830       * @param int $offset The offset
 831       * @param string $group The group name
 832       * @param string $label The text to use if there's no message for them.
 833       *
 834       * @return string
 835       */
 836  	function getLinkLabel( $offset, $group, $label ) {
 837          $message = "cite_link_label_group-$group";
 838          if ( !isset( $this->mLinkLabels[$group] ) ) {
 839              $this->genLinkLabels( $group, $message );
 840          }
 841          if ( $this->mLinkLabels[$group] === false ) {
 842              // Use normal representation, ie. "$group 1", "$group 2"...
 843              return $label;
 844          }
 845  
 846          if ( isset( $this->mLinkLabels[$group][$offset - 1] ) ) {
 847              return $this->mLinkLabels[$group][$offset - 1];
 848          } else {
 849              // Feed me!
 850              return $this->error( 'cite_error_no_link_label_group', array( $group, $message ), 'noparse' );
 851          }
 852      }
 853  
 854      /**
 855       * Return an id for use in wikitext output based on a key and
 856       * optionally the number of it, used in <references>, not <ref>
 857       * (since otherwise it would link to itself)
 858       *
 859       * @static
 860       *
 861       * @param string $key The key
 862       * @param int $num The number of the key
 863       * @return string A key for use in wikitext
 864       */
 865  	function refKey( $key, $num = null ) {
 866          $prefix = wfMessage( 'cite_reference_link_prefix' )->inContentLanguage()->text();
 867          $suffix = wfMessage( 'cite_reference_link_suffix' )->inContentLanguage()->text();
 868          if ( isset( $num ) ) {
 869              $key = wfMessage( 'cite_reference_link_key_with_num', $key, $num )
 870                  ->inContentLanguage()->plain();
 871          }
 872  
 873          return "$prefix$key$suffix";
 874      }
 875  
 876      /**
 877       * Return an id for use in wikitext output based on a key and
 878       * optionally the number of it, used in <ref>, not <references>
 879       * (since otherwise it would link to itself)
 880       *
 881       * @static
 882       *
 883       * @param string $key The key
 884       * @param int $num The number of the key
 885       * @return string A key for use in wikitext
 886       */
 887  	function referencesKey( $key, $num = null ) {
 888          $prefix = wfMessage( 'cite_references_link_prefix' )->inContentLanguage()->text();
 889          $suffix = wfMessage( 'cite_references_link_suffix' )->inContentLanguage()->text();
 890          if ( isset( $num ) ) {
 891              $key = wfMessage( 'cite_reference_link_key_with_num', $key, $num )
 892                  ->inContentLanguage()->plain();
 893          }
 894  
 895          return "$prefix$key$suffix";
 896      }
 897  
 898      /**
 899       * Generate a link (<sup ...) for the <ref> element from a key
 900       * and return XHTML ready for output
 901       *
 902       * @param $group
 903       * @param $key string The key for the link
 904       * @param $count int The index of the key, used for distinguishing
 905       *                   multiple occurrences of the same key
 906       * @param $label int The label to use for the link, I want to
 907       *                   use the same label for all occourances of
 908       *                   the same named reference.
 909       * @param $subkey string
 910       *
 911       * @return string
 912       */
 913  	function linkRef( $group, $key, $count = null, $label = null, $subkey = '' ) {
 914          global $wgContLang;
 915          $label = is_null( $label ) ? ++$this->mGroupCnt[$group] : $label;
 916  
 917          return
 918              $this->mParser->recursiveTagParse(
 919                  wfMessage(
 920                      'cite_reference_link',
 921                      $this->refKey( $key, $count ),
 922                      $this->referencesKey( $key . $subkey ),
 923                      $this->getLinkLabel( $label, $group,
 924                          ( ( $group == CITE_DEFAULT_GROUP ) ? '' : "$group " ) . $wgContLang->formatNum( $label ) )
 925                  )->inContentLanguage()->plain()
 926              );
 927      }
 928  
 929      /**
 930       * This does approximately the same thing as
 931       * Language::listToText() but due to this being used for a
 932       * slightly different purpose (people might not want , as the
 933       * first separator and not 'and' as the second, and this has to
 934       * use messages from the content language) I'm rolling my own.
 935       *
 936       * @static
 937       *
 938       * @param array $arr The array to format
 939       * @return string
 940       */
 941  	function listToText( $arr ) {
 942          $cnt = count( $arr );
 943  
 944          $sep = wfMessage( 'cite_references_link_many_sep' )->inContentLanguage()->plain();
 945          $and = wfMessage( 'cite_references_link_many_and' )->inContentLanguage()->plain();
 946  
 947          if ( $cnt == 1 ) {
 948              // Enforce always returning a string
 949              return (string)$arr[0];
 950          } else {
 951              $t = array_slice( $arr, 0, $cnt - 1 );
 952              return implode( $sep, $t ) . $and . $arr[$cnt - 1];
 953          }
 954      }
 955  
 956      /**
 957       * Generate the labels to pass to the
 958       * 'cite_references_link_many_format' message, the format is an
 959       * arbitrary number of tokens separated by [\t\n ]
 960       */
 961  	function genBacklinkLabels() {
 962          wfProfileIn( __METHOD__ );
 963          $text = wfMessage( 'cite_references_link_many_format_backlink_labels' )
 964              ->inContentLanguage()->plain();
 965          $this->mBacklinkLabels = preg_split( '#[\n\t ]#', $text );
 966          wfProfileOut( __METHOD__ );
 967      }
 968  
 969      /**
 970       * Generate the labels to pass to the
 971       * 'cite_reference_link' message instead of numbers, the format is an
 972       * arbitrary number of tokens separated by [\t\n ]
 973       *
 974       * @param $group
 975       * @param $message
 976       */
 977  	function genLinkLabels( $group, $message ) {
 978          wfProfileIn( __METHOD__ );
 979          $text = false;
 980          $msg = wfMessage( $message )->inContentLanguage();
 981          if ( $msg->exists() ) {
 982              $text = $msg->plain();
 983          }
 984          $this->mLinkLabels[$group] = ( $text == '' ) ? false : preg_split( '#[\n\t ]#', $text );
 985          wfProfileOut( __METHOD__ );
 986      }
 987  
 988      /**
 989       * Gets run when Parser::clearState() gets run, since we don't
 990       * want the counts to transcend pages and other instances
 991       *
 992       * @param $parser Parser
 993       *
 994       * @return bool
 995       */
 996  	function clearState( &$parser ) {
 997          if ( $parser->extCite !== $this ) {
 998              return $parser->extCite->clearState( $parser );
 999          }
1000  
1001          # Don't clear state when we're in the middle of parsing
1002          # a <ref> tag
1003          if ( $this->mInCite || $this->mInReferences ) {
1004              return true;
1005          }
1006  
1007          $this->mGroupCnt = array();
1008          $this->mOutCnt = 0;
1009          $this->mCallCnt = 0;
1010          $this->mRefs = array();
1011          $this->mReferencesErrors = array();
1012          $this->mRefCallStack = array();
1013  
1014          return true;
1015      }
1016  
1017      /**
1018       * Gets run when the parser is cloned.
1019       *
1020       * @param $parser Parser
1021       *
1022       * @return bool
1023       */
1024  	function cloneState( $parser ) {
1025          if ( $parser->extCite !== $this ) {
1026              return $parser->extCite->cloneState( $parser );
1027          }
1028  
1029          $parser->extCite = clone $this;
1030          $parser->setHook( 'ref' , array( $parser->extCite, 'ref' ) );
1031          $parser->setHook( 'references' , array( $parser->extCite, 'references' ) );
1032  
1033          // Clear the state, making sure it will actually work.
1034          $parser->extCite->mInCite = false;
1035          $parser->extCite->mInReferences = false;
1036          $parser->extCite->clearState( $parser );
1037  
1038          return true;
1039      }
1040  
1041      /**
1042       * Called at the end of page processing to append an error if refs were
1043       * used without a references tag.
1044       *
1045       * @param $afterParse bool  true if called from the ParserAfterParse hook
1046       * @param $parser Parser
1047       * @param $text string
1048       *
1049       * @return bool
1050       */
1051  	function checkRefsNoReferences( $afterParse, &$parser, &$text ) {
1052          if ( $parser->extCite !== $this ) {
1053              return $parser->extCite->checkRefsNoReferences( $afterParse, $parser, $text );
1054          }
1055  
1056          if ( $afterParse ) {
1057              $this->mHaveAfterParse = true;
1058          } elseif ( $this->mHaveAfterParse ) {
1059              return true;
1060          }
1061  
1062          if ( $parser->getOptions()->getIsSectionPreview() ) {
1063              return true;
1064          }
1065  
1066          foreach ( $this->mRefs as $group => $refs ) {
1067              if ( count( $refs ) == 0 ) {
1068                  continue;
1069              }
1070              if ( $group == CITE_DEFAULT_GROUP ) {
1071                  $text .= $this->referencesFormat( $group, '', '' );
1072              } else {
1073                  $text .= "\n<br />" . $this->error( 'cite_error_group_refs_without_references', htmlspecialchars( $group ) );
1074              }
1075          }
1076          return true;
1077      }
1078  
1079      /**
1080       * Hook for the InlineEditor extension. If any ref or reference reference tag is in the text, the entire
1081       * page should be reparsed, so we return false in that case.
1082       *
1083       * @param $output
1084       *
1085       * @return bool
1086       */
1087  	function checkAnyCalls( &$output ) {
1088          global $wgParser;
1089          /* InlineEditor always uses $wgParser */
1090          return ( $wgParser->extCite->mCallCnt <= 0 );
1091      }
1092  
1093      /**
1094       * Initialize the parser hooks
1095       *
1096       * @param $parser Parser
1097       *
1098       * @return bool
1099       */
1100  	static function setHooks( $parser ) {
1101          global $wgHooks;
1102  
1103          $parser->extCite = new self();
1104  
1105          if ( !Cite::$hooksInstalled ) {
1106              $wgHooks['ParserClearState'][] = array( $parser->extCite, 'clearState' );
1107              $wgHooks['ParserCloned'][] = array( $parser->extCite, 'cloneState' );
1108              $wgHooks['ParserAfterParse'][] = array( $parser->extCite, 'checkRefsNoReferences', true );
1109              $wgHooks['ParserBeforeTidy'][] = array( $parser->extCite, 'checkRefsNoReferences', false );
1110              $wgHooks['InlineEditorPartialAfterParse'][] = array( $parser->extCite, 'checkAnyCalls' );
1111              Cite::$hooksInstalled = true;
1112          }
1113          $parser->setHook( 'ref' , array( $parser->extCite, 'ref' ) );
1114          $parser->setHook( 'references' , array( $parser->extCite, 'references' ) );
1115  
1116          return true;
1117      }
1118  
1119      /**
1120       * Return an error message based on an error ID
1121       *
1122       * @param string $key   Message name for the error
1123       * @param string $param Parameter to pass to the message
1124       * @param string $parse Whether to parse the message ('parse') or not ('noparse')
1125       * @return string XHTML or wikitext ready for output
1126       */
1127  	function error( $key, $param = null, $parse = 'parse' ) {
1128          # We rely on the fact that PHP is okay with passing unused argu-
1129          # ments to functions.  If $1 is not used in the message, wfMessage will
1130          # just ignore the extra parameter.
1131          $ret = '<strong class="error mw-ext-cite-error">' .
1132              wfMessage( 'cite_error', wfMessage( $key, $param )->inContentLanguage()->plain() )->inContentLanguage()->plain() .
1133              '</strong>';
1134          if ( $parse == 'parse' ) {
1135              $ret = $this->mParser->recursiveTagParse( $ret );
1136          }
1137          return $ret;
1138      }
1139  
1140      /**#@-*/
1141  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1