MediaWiki  1.23.16
Parser.php
Go to the documentation of this file.
1 <?php
67 class Parser {
73  const VERSION = '1.6.4';
74 
79  const HALF_PARSED_VERSION = 2;
80 
81  # Flags for Parser::setFunctionHook
82  # Also available as global constants from Defines.php
83  const SFH_NO_HASH = 1;
84  const SFH_OBJECT_ARGS = 2;
85 
86  # Constants needed for external link processing
87  # Everything except bracket, space, or control characters
88  # \p{Zs} is unicode 'separator, space' category. It covers the space 0x20
89  # as well as U+3000 is IDEOGRAPHIC SPACE for bug 19052
90  const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F\p{Zs}]';
91  const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)([^][<>"\\x00-\\x20\\x7F\p{Zs}]+)
92  \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sxu';
93 
94  # State constants for the definition list colon extraction
95  const COLON_STATE_TEXT = 0;
96  const COLON_STATE_TAG = 1;
97  const COLON_STATE_TAGSTART = 2;
98  const COLON_STATE_CLOSETAG = 3;
99  const COLON_STATE_TAGSLASH = 4;
100  const COLON_STATE_COMMENT = 5;
101  const COLON_STATE_COMMENTDASH = 6;
102  const COLON_STATE_COMMENTDASHDASH = 7;
103 
104  # Flags for preprocessToDom
105  const PTD_FOR_INCLUSION = 1;
106 
107  # Allowed values for $this->mOutputType
108  # Parameter to startExternalParse().
109  const OT_HTML = 1; # like parse()
110  const OT_WIKI = 2; # like preSaveTransform()
111  const OT_PREPROCESS = 3; # like preprocess()
112  const OT_MSG = 3;
113  const OT_PLAIN = 4; # like extractSections() - portions of the original are returned unchanged.
114 
115  # Marker Suffix needs to be accessible staticly.
116  # Must have a character that needs escaping in attributes (since 1.25.6)
117  const MARKER_SUFFIX = "-QINU`\"'\x7f";
118 
119  # Markers used for wrapping the table of contents
120  const TOC_START = '<mw:toc>';
121  const TOC_END = '</mw:toc>';
122 
123  # Persistent:
124  var $mTagHooks = array();
125  var $mTransparentTagHooks = array();
126  var $mFunctionHooks = array();
127  var $mFunctionSynonyms = array( 0 => array(), 1 => array() );
128  var $mFunctionTagHooks = array();
129  var $mStripList = array();
130  var $mDefaultStripList = array();
131  var $mVarCache = array();
132  var $mImageParams = array();
133  var $mImageParamsMagicArray = array();
134  var $mMarkerIndex = 0;
135  var $mFirstCall = true;
136 
137  # Initialised by initialiseVariables()
138 
142  var $mVariables;
143 
147  var $mSubstWords;
148  var $mConf, $mPreprocessor, $mExtLinkBracketedRegex, $mUrlProtocols; # Initialised in constructor
149 
150  # Cleared with clearState():
151 
154  var $mOutput;
155  var $mAutonumber, $mDTopen;
156 
160  var $mStripState;
161 
162  var $mIncludeCount, $mArgStack, $mLastSection, $mInPre;
166  var $mLinkHolders;
167 
168  var $mLinkID;
169  var $mIncludeSizes, $mPPNodeCount, $mGeneratedPPNodeCount, $mHighestExpansionDepth;
170  var $mDefaultSort;
171  var $mTplExpandCache; # empty-frame expansion cache
172  var $mTplRedirCache, $mTplDomCache, $mHeadings, $mDoubleUnderscores;
173  var $mExpensiveFunctionCount; # number of expensive parser function calls
174  var $mShowToc, $mForceTocPosition;
175 
179  var $mUser; # User object; only used when doing pre-save transform
180 
181  # Temporary
182  # These are variables reset at least once per parse regardless of $clearState
183 
187  var $mOptions;
188 
192  var $mTitle; # Title context, used for self-link rendering and similar things
193  var $mOutputType; # Output type, one of the OT_xxx constants
194  var $ot; # Shortcut alias, see setOutputType()
195  var $mRevisionObject; # The revision object of the specified revision ID
196  var $mRevisionId; # ID to display in {{REVISIONID}} tags
197  var $mRevisionTimestamp; # The timestamp of the specified revision ID
198  var $mRevisionUser; # User to display in {{REVISIONUSER}} tag
199  var $mRevisionSize; # Size to display in {{REVISIONSIZE}} variable
200  var $mRevIdForTs; # The revision ID which was used to fetch the timestamp
201  var $mInputSize = false; # For {{PAGESIZE}} on current page.
202 
206  var $mUniqPrefix;
207 
213  var $mLangLinkLanguages;
214 
220  public function __construct( $conf = array() ) {
221  $this->mConf = $conf;
222  $this->mUrlProtocols = wfUrlProtocols();
223  $this->mExtLinkBracketedRegex = '/\[(((?i)' . $this->mUrlProtocols . ')' .
224  self::EXT_LINK_URL_CLASS . '+)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F]*?)\]/Su';
225  if ( isset( $conf['preprocessorClass'] ) ) {
226  $this->mPreprocessorClass = $conf['preprocessorClass'];
227  } elseif ( defined( 'HPHP_VERSION' ) ) {
228  # Preprocessor_Hash is much faster than Preprocessor_DOM under HipHop
229  $this->mPreprocessorClass = 'Preprocessor_Hash';
230  } elseif ( extension_loaded( 'domxml' ) ) {
231  # PECL extension that conflicts with the core DOM extension (bug 13770)
232  wfDebug( "Warning: you have the obsolete domxml extension for PHP. Please remove it!\n" );
233  $this->mPreprocessorClass = 'Preprocessor_Hash';
234  } elseif ( extension_loaded( 'dom' ) ) {
235  $this->mPreprocessorClass = 'Preprocessor_DOM';
236  } else {
237  $this->mPreprocessorClass = 'Preprocessor_Hash';
238  }
239  wfDebug( __CLASS__ . ": using preprocessor: {$this->mPreprocessorClass}\n" );
240  }
241 
245  function __destruct() {
246  if ( isset( $this->mLinkHolders ) ) {
247  unset( $this->mLinkHolders );
248  }
249  foreach ( $this as $name => $value ) {
250  unset( $this->$name );
251  }
252  }
253 
257  function __clone() {
258  wfRunHooks( 'ParserCloned', array( $this ) );
259  }
260 
264  function firstCallInit() {
265  if ( !$this->mFirstCall ) {
266  return;
267  }
268  $this->mFirstCall = false;
269 
270  wfProfileIn( __METHOD__ );
271 
273  CoreTagHooks::register( $this );
274  $this->initialiseVariables();
275 
276  wfRunHooks( 'ParserFirstCallInit', array( &$this ) );
277  wfProfileOut( __METHOD__ );
278  }
279 
285  function clearState() {
286  wfProfileIn( __METHOD__ );
287  if ( $this->mFirstCall ) {
288  $this->firstCallInit();
289  }
290  $this->mOutput = new ParserOutput;
291  $this->mOptions->registerWatcher( array( $this->mOutput, 'recordOption' ) );
292  $this->mAutonumber = 0;
293  $this->mLastSection = '';
294  $this->mDTopen = false;
295  $this->mIncludeCount = array();
296  $this->mArgStack = false;
297  $this->mInPre = false;
298  $this->mLinkHolders = new LinkHolderArray( $this );
299  $this->mLinkID = 0;
300  $this->mRevisionObject = $this->mRevisionTimestamp =
301  $this->mRevisionId = $this->mRevisionUser = $this->mRevisionSize = null;
302  $this->mVarCache = array();
303  $this->mUser = null;
304  $this->mLangLinkLanguages = array();
305 
316  $this->mUniqPrefix = "\x7f'\"`UNIQ" . self::getRandomString();
317  $this->mStripState = new StripState( $this->mUniqPrefix );
318 
319  # Clear these on every parse, bug 4549
320  $this->mTplExpandCache = $this->mTplRedirCache = $this->mTplDomCache = array();
321 
322  $this->mShowToc = true;
323  $this->mForceTocPosition = false;
324  $this->mIncludeSizes = array(
325  'post-expand' => 0,
326  'arg' => 0,
327  );
328  $this->mPPNodeCount = 0;
329  $this->mGeneratedPPNodeCount = 0;
330  $this->mHighestExpansionDepth = 0;
331  $this->mDefaultSort = false;
332  $this->mHeadings = array();
333  $this->mDoubleUnderscores = array();
334  $this->mExpensiveFunctionCount = 0;
335 
336  # Fix cloning
337  if ( isset( $this->mPreprocessor ) && $this->mPreprocessor->parser !== $this ) {
338  $this->mPreprocessor = null;
339  }
340 
341  wfRunHooks( 'ParserClearState', array( &$this ) );
342  wfProfileOut( __METHOD__ );
343  }
344 
357  public function parse( $text, Title $title, ParserOptions $options, $linestart = true, $clearState = true, $revid = null ) {
363  global $wgUseTidy, $wgAlwaysUseTidy, $wgShowHostnames;
364  $fname = __METHOD__ . '-' . wfGetCaller();
365  wfProfileIn( __METHOD__ );
366  wfProfileIn( $fname );
367 
368  $this->startParse( $title, $options, self::OT_HTML, $clearState );
369 
370  $this->mInputSize = strlen( $text );
371  if ( $this->mOptions->getEnableLimitReport() ) {
372  $this->mOutput->resetParseStartTime();
373  }
374 
375  # Remove the strip marker tag prefix from the input, if present.
376  if ( $clearState ) {
377  $text = str_replace( $this->mUniqPrefix, '', $text );
378  }
379 
380  $oldRevisionId = $this->mRevisionId;
381  $oldRevisionObject = $this->mRevisionObject;
382  $oldRevisionTimestamp = $this->mRevisionTimestamp;
383  $oldRevisionUser = $this->mRevisionUser;
384  $oldRevisionSize = $this->mRevisionSize;
385  if ( $revid !== null ) {
386  $this->mRevisionId = $revid;
387  $this->mRevisionObject = null;
388  $this->mRevisionTimestamp = null;
389  $this->mRevisionUser = null;
390  $this->mRevisionSize = null;
391  }
392 
393  wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
394  # No more strip!
395  wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
396  $text = $this->internalParse( $text );
397  wfRunHooks( 'ParserAfterParse', array( &$this, &$text, &$this->mStripState ) );
398 
399  $text = $this->mStripState->unstripGeneral( $text );
400 
401  # Clean up special characters, only run once, next-to-last before doBlockLevels
402  $fixtags = array(
403  # french spaces, last one Guillemet-left
404  # only if there is something before the space
405  '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&#160;',
406  # french spaces, Guillemet-right
407  '/(\\302\\253) /' => '\\1&#160;',
408  '/&#160;(!\s*important)/' => ' \\1', # Beware of CSS magic word !important, bug #11874.
409  );
410  $text = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text );
411 
412  $text = $this->doBlockLevels( $text, $linestart );
413 
414  $this->replaceLinkHolders( $text );
415 
423  if ( !( $options->getDisableContentConversion()
424  || isset( $this->mDoubleUnderscores['nocontentconvert'] ) )
425  ) {
426  if ( !$this->mOptions->getInterfaceMessage() ) {
427  # The position of the convert() call should not be changed. it
428  # assumes that the links are all replaced and the only thing left
429  # is the <nowiki> mark.
430  $text = $this->getConverterLanguage()->convert( $text );
431  }
432  }
433 
441  if ( !( $options->getDisableTitleConversion()
442  || isset( $this->mDoubleUnderscores['nocontentconvert'] )
443  || isset( $this->mDoubleUnderscores['notitleconvert'] )
444  || $this->mOutput->getDisplayTitle() !== false )
445  ) {
446  $convruletitle = $this->getConverterLanguage()->getConvRuleTitle();
447  if ( $convruletitle ) {
448  $this->mOutput->setTitleText( $convruletitle );
449  } else {
450  $titleText = $this->getConverterLanguage()->convertTitle( $title );
451  $this->mOutput->setTitleText( $titleText );
452  }
453  }
454 
455  $text = $this->mStripState->unstripNoWiki( $text );
456 
457  wfRunHooks( 'ParserBeforeTidy', array( &$this, &$text ) );
458 
459  $text = $this->replaceTransparentTags( $text );
460  $text = $this->mStripState->unstripGeneral( $text );
461 
462  $text = Sanitizer::normalizeCharReferences( $text );
463 
464  if ( ( $wgUseTidy && $this->mOptions->getTidy() ) || $wgAlwaysUseTidy ) {
465  $text = MWTidy::tidy( $text );
466  } else {
467  # attempt to sanitize at least some nesting problems
468  # (bug #2702 and quite a few others)
469  $tidyregs = array(
470  # ''Something [http://www.cool.com cool''] -->
471  # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
472  '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
473  '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
474  # fix up an anchor inside another anchor, only
475  # at least for a single single nested link (bug 3695)
476  '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
477  '\\1\\2</a>\\3</a>\\1\\4</a>',
478  # fix div inside inline elements- doBlockLevels won't wrap a line which
479  # contains a div, so fix it up here; replace
480  # div with escaped text
481  '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
482  '\\1\\3&lt;div\\5&gt;\\6&lt;/div&gt;\\8\\9',
483  # remove empty italic or bold tag pairs, some
484  # introduced by rules above
485  '/<([bi])><\/\\1>/' => '',
486  );
487 
488  $text = preg_replace(
489  array_keys( $tidyregs ),
490  array_values( $tidyregs ),
491  $text );
492  }
493 
494  if ( $this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit() ) {
495  $this->limitationWarn( 'expensive-parserfunction',
496  $this->mExpensiveFunctionCount,
497  $this->mOptions->getExpensiveParserFunctionLimit()
498  );
499  }
500 
501  wfRunHooks( 'ParserAfterTidy', array( &$this, &$text ) );
502 
503  # Information on include size limits, for the benefit of users who try to skirt them
504  if ( $this->mOptions->getEnableLimitReport() ) {
505  $max = $this->mOptions->getMaxIncludeSize();
506 
507  $cpuTime = $this->mOutput->getTimeSinceStart( 'cpu' );
508  if ( $cpuTime !== null ) {
509  $this->mOutput->setLimitReportData( 'limitreport-cputime',
510  sprintf( "%.3f", $cpuTime )
511  );
512  }
513 
514  $wallTime = $this->mOutput->getTimeSinceStart( 'wall' );
515  $this->mOutput->setLimitReportData( 'limitreport-walltime',
516  sprintf( "%.3f", $wallTime )
517  );
518 
519  $this->mOutput->setLimitReportData( 'limitreport-ppvisitednodes',
520  array( $this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount() )
521  );
522  $this->mOutput->setLimitReportData( 'limitreport-ppgeneratednodes',
523  array( $this->mGeneratedPPNodeCount, $this->mOptions->getMaxGeneratedPPNodeCount() )
524  );
525  $this->mOutput->setLimitReportData( 'limitreport-postexpandincludesize',
526  array( $this->mIncludeSizes['post-expand'], $max )
527  );
528  $this->mOutput->setLimitReportData( 'limitreport-templateargumentsize',
529  array( $this->mIncludeSizes['arg'], $max )
530  );
531  $this->mOutput->setLimitReportData( 'limitreport-expansiondepth',
532  array( $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() )
533  );
534  $this->mOutput->setLimitReportData( 'limitreport-expensivefunctioncount',
535  array( $this->mExpensiveFunctionCount, $this->mOptions->getExpensiveParserFunctionLimit() )
536  );
537  wfRunHooks( 'ParserLimitReportPrepare', array( $this, $this->mOutput ) );
538 
539  $limitReport = "NewPP limit report\n";
540  if ( $wgShowHostnames ) {
541  $limitReport .= 'Parsed by ' . wfHostname() . "\n";
542  }
543  foreach ( $this->mOutput->getLimitReportData() as $key => $value ) {
544  if ( wfRunHooks( 'ParserLimitReportFormat',
545  array( $key, &$value, &$limitReport, false, false )
546  ) ) {
547  $keyMsg = wfMessage( $key )->inLanguage( 'en' )->useDatabase( false );
548  $valueMsg = wfMessage( array( "$key-value-text", "$key-value" ) )
549  ->inLanguage( 'en' )->useDatabase( false );
550  if ( !$valueMsg->exists() ) {
551  $valueMsg = new RawMessage( '$1' );
552  }
553  if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
554  $valueMsg->params( $value );
555  $limitReport .= "{$keyMsg->text()}: {$valueMsg->text()}\n";
556  }
557  }
558  }
559  // Since we're not really outputting HTML, decode the entities and
560  // then re-encode the things that need hiding inside HTML comments.
561  $limitReport = htmlspecialchars_decode( $limitReport );
562  wfRunHooks( 'ParserLimitReport', array( $this, &$limitReport ) );
563 
564  // Sanitize for comment. Note '‐' in the replacement is U+2010,
565  // which looks much like the problematic '-'.
566  $limitReport = str_replace( array( '-', '&' ), array( '‐', '&amp;' ), $limitReport );
567  $text .= "\n<!-- \n$limitReport-->\n";
568 
569  if ( $this->mGeneratedPPNodeCount > $this->mOptions->getMaxGeneratedPPNodeCount() / 10 ) {
570  wfDebugLog( 'generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' .
571  $this->mTitle->getPrefixedDBkey() );
572  }
573  }
574  $this->mOutput->setText( $text );
575 
576  $this->mRevisionId = $oldRevisionId;
577  $this->mRevisionObject = $oldRevisionObject;
578  $this->mRevisionTimestamp = $oldRevisionTimestamp;
579  $this->mRevisionUser = $oldRevisionUser;
580  $this->mRevisionSize = $oldRevisionSize;
581  $this->mInputSize = false;
582  wfProfileOut( $fname );
583  wfProfileOut( __METHOD__ );
584 
585  return $this->mOutput;
586  }
587 
599  function recursiveTagParse( $text, $frame = false ) {
600  wfProfileIn( __METHOD__ );
601  wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
602  wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
603  $text = $this->internalParse( $text, false, $frame );
604  wfProfileOut( __METHOD__ );
605  return $text;
606  }
607 
613  function preprocess( $text, Title $title = null, ParserOptions $options, $revid = null ) {
614  wfProfileIn( __METHOD__ );
615  $this->startParse( $title, $options, self::OT_PREPROCESS, true );
616  if ( $revid !== null ) {
617  $this->mRevisionId = $revid;
618  }
619  wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
620  wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
621  $text = $this->replaceVariables( $text );
622  $text = $this->mStripState->unstripBoth( $text );
623  wfProfileOut( __METHOD__ );
624  return $text;
625  }
626 
636  public function recursivePreprocess( $text, $frame = false ) {
637  wfProfileIn( __METHOD__ );
638  $text = $this->replaceVariables( $text, $frame );
639  $text = $this->mStripState->unstripBoth( $text );
640  wfProfileOut( __METHOD__ );
641  return $text;
642  }
643 
657  public function getPreloadText( $text, Title $title, ParserOptions $options, $params = array() ) {
658  $msg = new RawMessage( $text );
659  $text = $msg->params( $params )->plain();
660 
661  # Parser (re)initialisation
662  $this->startParse( $title, $options, self::OT_PLAIN, true );
663 
665  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
666  $text = $this->getPreprocessor()->newFrame()->expand( $dom, $flags );
667  $text = $this->mStripState->unstripBoth( $text );
668  return $text;
669  }
670 
676  public static function getRandomString() {
677  return wfRandomString( 16 );
678  }
679 
686  function setUser( $user ) {
687  $this->mUser = $user;
688  }
689 
695  public function uniqPrefix() {
696  if ( !isset( $this->mUniqPrefix ) ) {
697  # @todo FIXME: This is probably *horribly wrong*
698  # LanguageConverter seems to want $wgParser's uniqPrefix, however
699  # if this is called for a parser cache hit, the parser may not
700  # have ever been initialized in the first place.
701  # Not really sure what the heck is supposed to be going on here.
702  return '';
703  # throw new MWException( "Accessing uninitialized mUniqPrefix" );
704  }
705  return $this->mUniqPrefix;
706  }
707 
713  function setTitle( $t ) {
714  if ( !$t || $t instanceof FakeTitle ) {
715  $t = Title::newFromText( 'NO TITLE' );
716  }
717 
718  if ( $t->hasFragment() ) {
719  # Strip the fragment to avoid various odd effects
720  $this->mTitle = clone $t;
721  $this->mTitle->setFragment( '' );
722  } else {
723  $this->mTitle = $t;
724  }
725  }
726 
732  function getTitle() {
733  return $this->mTitle;
734  }
735 
742  function Title( $x = null ) {
743  return wfSetVar( $this->mTitle, $x );
744  }
745 
751  function setOutputType( $ot ) {
752  $this->mOutputType = $ot;
753  # Shortcut alias
754  $this->ot = array(
755  'html' => $ot == self::OT_HTML,
756  'wiki' => $ot == self::OT_WIKI,
757  'pre' => $ot == self::OT_PREPROCESS,
758  'plain' => $ot == self::OT_PLAIN,
759  );
760  }
761 
768  function OutputType( $x = null ) {
769  return wfSetVar( $this->mOutputType, $x );
770  }
771 
777  function getOutput() {
778  return $this->mOutput;
779  }
780 
786  function getOptions() {
787  return $this->mOptions;
788  }
789 
796  function Options( $x = null ) {
797  return wfSetVar( $this->mOptions, $x );
798  }
799 
803  function nextLinkID() {
804  return $this->mLinkID++;
805  }
806 
810  function setLinkID( $id ) {
811  $this->mLinkID = $id;
812  }
813 
818  function getFunctionLang() {
819  return $this->getTargetLanguage();
820  }
821 
831  public function getTargetLanguage() {
832  $target = $this->mOptions->getTargetLanguage();
833 
834  if ( $target !== null ) {
835  return $target;
836  } elseif ( $this->mOptions->getInterfaceMessage() ) {
837  return $this->mOptions->getUserLangObj();
838  } elseif ( is_null( $this->mTitle ) ) {
839  throw new MWException( __METHOD__ . ': $this->mTitle is null' );
840  }
841 
842  return $this->mTitle->getPageLanguage();
843  }
844 
848  function getConverterLanguage() {
849  return $this->getTargetLanguage();
850  }
851 
858  function getUser() {
859  if ( !is_null( $this->mUser ) ) {
860  return $this->mUser;
861  }
862  return $this->mOptions->getUser();
863  }
864 
870  function getPreprocessor() {
871  if ( !isset( $this->mPreprocessor ) ) {
872  $class = $this->mPreprocessorClass;
873  $this->mPreprocessor = new $class( $this );
874  }
875  return $this->mPreprocessor;
876  }
877 
898  public static function extractTagsAndParams( $elements, $text, &$matches, $uniq_prefix = '' ) {
899  static $n = 1;
900  $stripped = '';
901  $matches = array();
902 
903  $taglist = implode( '|', $elements );
904  $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?" . ">)|<(!--)/i";
905 
906  while ( $text != '' ) {
907  $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
908  $stripped .= $p[0];
909  if ( count( $p ) < 5 ) {
910  break;
911  }
912  if ( count( $p ) > 5 ) {
913  # comment
914  $element = $p[4];
915  $attributes = '';
916  $close = '';
917  $inside = $p[5];
918  } else {
919  # tag
920  $element = $p[1];
921  $attributes = $p[2];
922  $close = $p[3];
923  $inside = $p[4];
924  }
925 
926  $marker = "$uniq_prefix-$element-" . sprintf( '%08X', $n++ ) . self::MARKER_SUFFIX;
927  $stripped .= $marker;
928 
929  if ( $close === '/>' ) {
930  # Empty element tag, <tag />
931  $content = null;
932  $text = $inside;
933  $tail = null;
934  } else {
935  if ( $element === '!--' ) {
936  $end = '/(-->)/';
937  } else {
938  $end = "/(<\\/$element\\s*>)/i";
939  }
940  $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
941  $content = $q[0];
942  if ( count( $q ) < 3 ) {
943  # No end tag -- let it run out to the end of the text.
944  $tail = '';
945  $text = '';
946  } else {
947  $tail = $q[1];
948  $text = $q[2];
949  }
950  }
951 
952  $matches[$marker] = array( $element,
953  $content,
954  Sanitizer::decodeTagAttributes( $attributes ),
955  "<$element$attributes$close$content$tail" );
956  }
957  return $stripped;
958  }
959 
965  function getStripList() {
966  return $this->mStripList;
967  }
968 
978  function insertStripItem( $text ) {
979  $rnd = "{$this->mUniqPrefix}-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
980  $this->mMarkerIndex++;
981  $this->mStripState->addGeneral( $rnd, $text );
982  return $rnd;
983  }
984 
991  function doTableStuff( $text ) {
992  wfProfileIn( __METHOD__ );
993 
994  $lines = StringUtils::explode( "\n", $text );
995  $out = '';
996  $td_history = array(); # Is currently a td tag open?
997  $last_tag_history = array(); # Save history of last lag activated (td, th or caption)
998  $tr_history = array(); # Is currently a tr tag open?
999  $tr_attributes = array(); # history of tr attributes
1000  $has_opened_tr = array(); # Did this table open a <tr> element?
1001  $indent_level = 0; # indent level of the table
1002 
1003  foreach ( $lines as $outLine ) {
1004  $line = trim( $outLine );
1005 
1006  if ( $line === '' ) { # empty line, go to next line
1007  $out .= $outLine . "\n";
1008  continue;
1009  }
1010 
1011  $first_character = $line[0];
1012  $matches = array();
1013 
1014  if ( preg_match( '/^(:*)\{\|(.*)$/', $line, $matches ) ) {
1015  # First check if we are starting a new table
1016  $indent_level = strlen( $matches[1] );
1017 
1018  $attributes = $this->mStripState->unstripBoth( $matches[2] );
1019  $attributes = Sanitizer::fixTagAttributes( $attributes, 'table' );
1020 
1021  $outLine = str_repeat( '<dl><dd>', $indent_level ) . "<table{$attributes}>";
1022  array_push( $td_history, false );
1023  array_push( $last_tag_history, '' );
1024  array_push( $tr_history, false );
1025  array_push( $tr_attributes, '' );
1026  array_push( $has_opened_tr, false );
1027  } elseif ( count( $td_history ) == 0 ) {
1028  # Don't do any of the following
1029  $out .= $outLine . "\n";
1030  continue;
1031  } elseif ( substr( $line, 0, 2 ) === '|}' ) {
1032  # We are ending a table
1033  $line = '</table>' . substr( $line, 2 );
1034  $last_tag = array_pop( $last_tag_history );
1035 
1036  if ( !array_pop( $has_opened_tr ) ) {
1037  $line = "<tr><td></td></tr>{$line}";
1038  }
1039 
1040  if ( array_pop( $tr_history ) ) {
1041  $line = "</tr>{$line}";
1042  }
1043 
1044  if ( array_pop( $td_history ) ) {
1045  $line = "</{$last_tag}>{$line}";
1046  }
1047  array_pop( $tr_attributes );
1048  $outLine = $line . str_repeat( '</dd></dl>', $indent_level );
1049  } elseif ( substr( $line, 0, 2 ) === '|-' ) {
1050  # Now we have a table row
1051  $line = preg_replace( '#^\|-+#', '', $line );
1052 
1053  # Whats after the tag is now only attributes
1054  $attributes = $this->mStripState->unstripBoth( $line );
1055  $attributes = Sanitizer::fixTagAttributes( $attributes, 'tr' );
1056  array_pop( $tr_attributes );
1057  array_push( $tr_attributes, $attributes );
1058 
1059  $line = '';
1060  $last_tag = array_pop( $last_tag_history );
1061  array_pop( $has_opened_tr );
1062  array_push( $has_opened_tr, true );
1063 
1064  if ( array_pop( $tr_history ) ) {
1065  $line = '</tr>';
1066  }
1067 
1068  if ( array_pop( $td_history ) ) {
1069  $line = "</{$last_tag}>{$line}";
1070  }
1071 
1072  $outLine = $line;
1073  array_push( $tr_history, false );
1074  array_push( $td_history, false );
1075  array_push( $last_tag_history, '' );
1076  } elseif ( $first_character === '|' || $first_character === '!' || substr( $line, 0, 2 ) === '|+' ) {
1077  # This might be cell elements, td, th or captions
1078  if ( substr( $line, 0, 2 ) === '|+' ) {
1079  $first_character = '+';
1080  $line = substr( $line, 1 );
1081  }
1082 
1083  $line = substr( $line, 1 );
1084 
1085  if ( $first_character === '!' ) {
1086  $line = str_replace( '!!', '||', $line );
1087  }
1088 
1089  # Split up multiple cells on the same line.
1090  # FIXME : This can result in improper nesting of tags processed
1091  # by earlier parser steps, but should avoid splitting up eg
1092  # attribute values containing literal "||".
1093  $cells = StringUtils::explodeMarkup( '||', $line );
1094 
1095  $outLine = '';
1096 
1097  # Loop through each table cell
1098  foreach ( $cells as $cell ) {
1099  $previous = '';
1100  if ( $first_character !== '+' ) {
1101  $tr_after = array_pop( $tr_attributes );
1102  if ( !array_pop( $tr_history ) ) {
1103  $previous = "<tr{$tr_after}>\n";
1104  }
1105  array_push( $tr_history, true );
1106  array_push( $tr_attributes, '' );
1107  array_pop( $has_opened_tr );
1108  array_push( $has_opened_tr, true );
1109  }
1110 
1111  $last_tag = array_pop( $last_tag_history );
1112 
1113  if ( array_pop( $td_history ) ) {
1114  $previous = "</{$last_tag}>\n{$previous}";
1115  }
1116 
1117  if ( $first_character === '|' ) {
1118  $last_tag = 'td';
1119  } elseif ( $first_character === '!' ) {
1120  $last_tag = 'th';
1121  } elseif ( $first_character === '+' ) {
1122  $last_tag = 'caption';
1123  } else {
1124  $last_tag = '';
1125  }
1126 
1127  array_push( $last_tag_history, $last_tag );
1128 
1129  # A cell could contain both parameters and data
1130  $cell_data = explode( '|', $cell, 2 );
1131 
1132  # Bug 553: Note that a '|' inside an invalid link should not
1133  # be mistaken as delimiting cell parameters
1134  if ( strpos( $cell_data[0], '[[' ) !== false ) {
1135  $cell = "{$previous}<{$last_tag}>{$cell}";
1136  } elseif ( count( $cell_data ) == 1 ) {
1137  $cell = "{$previous}<{$last_tag}>{$cell_data[0]}";
1138  } else {
1139  $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1140  $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1141  $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}";
1142  }
1143 
1144  $outLine .= $cell;
1145  array_push( $td_history, true );
1146  }
1147  }
1148  $out .= $outLine . "\n";
1149  }
1150 
1151  # Closing open td, tr && table
1152  while ( count( $td_history ) > 0 ) {
1153  if ( array_pop( $td_history ) ) {
1154  $out .= "</td>\n";
1155  }
1156  if ( array_pop( $tr_history ) ) {
1157  $out .= "</tr>\n";
1158  }
1159  if ( !array_pop( $has_opened_tr ) ) {
1160  $out .= "<tr><td></td></tr>\n";
1161  }
1162 
1163  $out .= "</table>\n";
1164  }
1165 
1166  # Remove trailing line-ending (b/c)
1167  if ( substr( $out, -1 ) === "\n" ) {
1168  $out = substr( $out, 0, -1 );
1169  }
1170 
1171  # special case: don't return empty table
1172  if ( $out === "<table>\n<tr><td></td></tr>\n</table>" ) {
1173  $out = '';
1174  }
1175 
1176  wfProfileOut( __METHOD__ );
1177 
1178  return $out;
1179  }
1180 
1193  function internalParse( $text, $isMain = true, $frame = false ) {
1194  wfProfileIn( __METHOD__ );
1195 
1196  $origText = $text;
1197 
1198  # Hook to suspend the parser in this state
1199  if ( !wfRunHooks( 'ParserBeforeInternalParse', array( &$this, &$text, &$this->mStripState ) ) ) {
1200  wfProfileOut( __METHOD__ );
1201  return $text;
1202  }
1203 
1204  # if $frame is provided, then use $frame for replacing any variables
1205  if ( $frame ) {
1206  # use frame depth to infer how include/noinclude tags should be handled
1207  # depth=0 means this is the top-level document; otherwise it's an included document
1208  if ( !$frame->depth ) {
1209  $flag = 0;
1210  } else {
1211  $flag = Parser::PTD_FOR_INCLUSION;
1212  }
1213  $dom = $this->preprocessToDom( $text, $flag );
1214  $text = $frame->expand( $dom );
1215  } else {
1216  # if $frame is not provided, then use old-style replaceVariables
1217  $text = $this->replaceVariables( $text );
1218  }
1219 
1220  wfRunHooks( 'InternalParseBeforeSanitize', array( &$this, &$text, &$this->mStripState ) );
1221  $text = Sanitizer::removeHTMLtags( $text, array( &$this, 'attributeStripCallback' ), false, array_keys( $this->mTransparentTagHooks ) );
1222  wfRunHooks( 'InternalParseBeforeLinks', array( &$this, &$text, &$this->mStripState ) );
1223 
1224  # Tables need to come after variable replacement for things to work
1225  # properly; putting them before other transformations should keep
1226  # exciting things like link expansions from showing up in surprising
1227  # places.
1228  $text = $this->doTableStuff( $text );
1229 
1230  $text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text );
1231 
1232  $text = $this->doDoubleUnderscore( $text );
1233 
1234  $text = $this->doHeadings( $text );
1235  $text = $this->replaceInternalLinks( $text );
1236  $text = $this->doAllQuotes( $text );
1237  $text = $this->replaceExternalLinks( $text );
1238 
1239  # replaceInternalLinks may sometimes leave behind
1240  # absolute URLs, which have to be masked to hide them from replaceExternalLinks
1241  $text = str_replace( $this->mUniqPrefix . 'NOPARSE', '', $text );
1242 
1243  $text = $this->doMagicLinks( $text );
1244  $text = $this->formatHeadings( $text, $origText, $isMain );
1245 
1246  wfProfileOut( __METHOD__ );
1247  return $text;
1248  }
1249 
1261  function doMagicLinks( $text ) {
1262  wfProfileIn( __METHOD__ );
1263  $prots = wfUrlProtocolsWithoutProtRel();
1264  $urlChar = self::EXT_LINK_URL_CLASS;
1265  $text = preg_replace_callback(
1266  '!(?: # Start cases
1267  (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1268  (<.*?>) | # m[2]: Skip stuff inside HTML elements' . "
1269  (\\b(?i:$prots)$urlChar+) | # m[3]: Free external links" . '
1270  (?:RFC|PMID)\s+([0-9]+) | # m[4]: RFC or PMID, capture number
1271  ISBN\s+(\b # m[5]: ISBN, capture number
1272  (?: 97[89] [\ \-]? )? # optional 13-digit ISBN prefix
1273  (?: [0-9] [\ \-]? ){9} # 9 digits with opt. delimiters
1274  [0-9Xx] # check digit
1275  \b)
1276  )!xu', array( &$this, 'magicLinkCallback' ), $text );
1277  wfProfileOut( __METHOD__ );
1278  return $text;
1279  }
1280 
1286  function magicLinkCallback( $m ) {
1287  if ( isset( $m[1] ) && $m[1] !== '' ) {
1288  # Skip anchor
1289  return $m[0];
1290  } elseif ( isset( $m[2] ) && $m[2] !== '' ) {
1291  # Skip HTML element
1292  return $m[0];
1293  } elseif ( isset( $m[3] ) && $m[3] !== '' ) {
1294  # Free external link
1295  return $this->makeFreeExternalLink( $m[0] );
1296  } elseif ( isset( $m[4] ) && $m[4] !== '' ) {
1297  # RFC or PMID
1298  if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
1299  $keyword = 'RFC';
1300  $urlmsg = 'rfcurl';
1301  $cssClass = 'mw-magiclink-rfc';
1302  $id = $m[4];
1303  } elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) {
1304  $keyword = 'PMID';
1305  $urlmsg = 'pubmedurl';
1306  $cssClass = 'mw-magiclink-pmid';
1307  $id = $m[4];
1308  } else {
1309  throw new MWException( __METHOD__ . ': unrecognised match type "' .
1310  substr( $m[0], 0, 20 ) . '"' );
1311  }
1312  $url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1313  return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $cssClass );
1314  } elseif ( isset( $m[5] ) && $m[5] !== '' ) {
1315  # ISBN
1316  $isbn = $m[5];
1317  $num = strtr( $isbn, array(
1318  '-' => '',
1319  ' ' => '',
1320  'x' => 'X',
1321  ));
1322  $titleObj = SpecialPage::getTitleFor( 'Booksources', $num );
1323  return '<a href="' .
1324  htmlspecialchars( $titleObj->getLocalURL() ) .
1325  "\" class=\"internal mw-magiclink-isbn\">ISBN $isbn</a>";
1326  } else {
1327  return $m[0];
1328  }
1329  }
1330 
1339  function makeFreeExternalLink( $url ) {
1340  wfProfileIn( __METHOD__ );
1341 
1342  $trail = '';
1343 
1344  # The characters '<' and '>' (which were escaped by
1345  # removeHTMLtags()) should not be included in
1346  # URLs, per RFC 2396.
1347  $m2 = array();
1348  if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
1349  $trail = substr( $url, $m2[0][1] ) . $trail;
1350  $url = substr( $url, 0, $m2[0][1] );
1351  }
1352 
1353  # Move trailing punctuation to $trail
1354  $sep = ',;\.:!?';
1355  # If there is no left bracket, then consider right brackets fair game too
1356  if ( strpos( $url, '(' ) === false ) {
1357  $sep .= ')';
1358  }
1359 
1360  $numSepChars = strspn( strrev( $url ), $sep );
1361  if ( $numSepChars ) {
1362  $trail = substr( $url, -$numSepChars ) . $trail;
1363  $url = substr( $url, 0, -$numSepChars );
1364  }
1365 
1366  $url = Sanitizer::cleanUrl( $url );
1367 
1368  # Is this an external image?
1369  $text = $this->maybeMakeExternalImage( $url );
1370  if ( $text === false ) {
1371  # Not an image, make a link
1372  $text = Linker::makeExternalLink( $url,
1373  $this->getConverterLanguage()->markNoConversion( $url, true ),
1374  true, 'free',
1375  $this->getExternalLinkAttribs( $url ) );
1376  # Register it in the output object...
1377  $this->mOutput->addExternalLink( $url );
1378  }
1379  wfProfileOut( __METHOD__ );
1380  return $text . $trail;
1381  }
1382 
1392  function doHeadings( $text ) {
1393  wfProfileIn( __METHOD__ );
1394  for ( $i = 6; $i >= 1; --$i ) {
1395  $h = str_repeat( '=', $i );
1396  $text = preg_replace( "/^$h(.+)$h\\s*$/m", "<h$i>\\1</h$i>", $text );
1397  }
1398  wfProfileOut( __METHOD__ );
1399  return $text;
1400  }
1401 
1410  function doAllQuotes( $text ) {
1411  wfProfileIn( __METHOD__ );
1412  $outtext = '';
1413  $lines = StringUtils::explode( "\n", $text );
1414  foreach ( $lines as $line ) {
1415  $outtext .= $this->doQuotes( $line ) . "\n";
1416  }
1417  $outtext = substr( $outtext, 0, -1 );
1418  wfProfileOut( __METHOD__ );
1419  return $outtext;
1420  }
1421 
1429  public function doQuotes( $text ) {
1430  $arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1431  $countarr = count( $arr );
1432  if ( $countarr == 1 ) {
1433  return $text;
1434  }
1435 
1436  // First, do some preliminary work. This may shift some apostrophes from
1437  // being mark-up to being text. It also counts the number of occurrences
1438  // of bold and italics mark-ups.
1439  $numbold = 0;
1440  $numitalics = 0;
1441  for ( $i = 1; $i < $countarr; $i += 2 ) {
1442  $thislen = strlen( $arr[$i] );
1443  // If there are ever four apostrophes, assume the first is supposed to
1444  // be text, and the remaining three constitute mark-up for bold text.
1445  // (bug 13227: ''''foo'''' turns into ' ''' foo ' ''')
1446  if ( $thislen == 4 ) {
1447  $arr[$i - 1] .= "'";
1448  $arr[$i] = "'''";
1449  $thislen = 3;
1450  } elseif ( $thislen > 5 ) {
1451  // If there are more than 5 apostrophes in a row, assume they're all
1452  // text except for the last 5.
1453  // (bug 13227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
1454  $arr[$i - 1] .= str_repeat( "'", $thislen - 5 );
1455  $arr[$i] = "'''''";
1456  $thislen = 5;
1457  }
1458  // Count the number of occurrences of bold and italics mark-ups.
1459  if ( $thislen == 2 ) {
1460  $numitalics++;
1461  } elseif ( $thislen == 3 ) {
1462  $numbold++;
1463  } elseif ( $thislen == 5 ) {
1464  $numitalics++;
1465  $numbold++;
1466  }
1467  }
1468 
1469  // If there is an odd number of both bold and italics, it is likely
1470  // that one of the bold ones was meant to be an apostrophe followed
1471  // by italics. Which one we cannot know for certain, but it is more
1472  // likely to be one that has a single-letter word before it.
1473  if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
1474  $firstsingleletterword = -1;
1475  $firstmultiletterword = -1;
1476  $firstspace = -1;
1477  for ( $i = 1; $i < $countarr; $i += 2 ) {
1478  if ( strlen( $arr[$i] ) == 3 ) {
1479  $x1 = substr( $arr[$i - 1], -1 );
1480  $x2 = substr( $arr[$i - 1], -2, 1 );
1481  if ( $x1 === ' ' ) {
1482  if ( $firstspace == -1 ) {
1483  $firstspace = $i;
1484  }
1485  } elseif ( $x2 === ' ' ) {
1486  if ( $firstsingleletterword == -1 ) {
1487  $firstsingleletterword = $i;
1488  // if $firstsingleletterword is set, we don't
1489  // look at the other options, so we can bail early.
1490  break;
1491  }
1492  } else {
1493  if ( $firstmultiletterword == -1 ) {
1494  $firstmultiletterword = $i;
1495  }
1496  }
1497  }
1498  }
1499 
1500  // If there is a single-letter word, use it!
1501  if ( $firstsingleletterword > -1 ) {
1502  $arr[$firstsingleletterword] = "''";
1503  $arr[$firstsingleletterword - 1] .= "'";
1504  } elseif ( $firstmultiletterword > -1 ) {
1505  // If not, but there's a multi-letter word, use that one.
1506  $arr[$firstmultiletterword] = "''";
1507  $arr[$firstmultiletterword - 1] .= "'";
1508  } elseif ( $firstspace > -1 ) {
1509  // ... otherwise use the first one that has neither.
1510  // (notice that it is possible for all three to be -1 if, for example,
1511  // there is only one pentuple-apostrophe in the line)
1512  $arr[$firstspace] = "''";
1513  $arr[$firstspace - 1] .= "'";
1514  }
1515  }
1516 
1517  // Now let's actually convert our apostrophic mush to HTML!
1518  $output = '';
1519  $buffer = '';
1520  $state = '';
1521  $i = 0;
1522  foreach ( $arr as $r ) {
1523  if ( ( $i % 2 ) == 0 ) {
1524  if ( $state === 'both' ) {
1525  $buffer .= $r;
1526  } else {
1527  $output .= $r;
1528  }
1529  } else {
1530  $thislen = strlen( $r );
1531  if ( $thislen == 2 ) {
1532  if ( $state === 'i' ) {
1533  $output .= '</i>';
1534  $state = '';
1535  } elseif ( $state === 'bi' ) {
1536  $output .= '</i>';
1537  $state = 'b';
1538  } elseif ( $state === 'ib' ) {
1539  $output .= '</b></i><b>';
1540  $state = 'b';
1541  } elseif ( $state === 'both' ) {
1542  $output .= '<b><i>' . $buffer . '</i>';
1543  $state = 'b';
1544  } else { // $state can be 'b' or ''
1545  $output .= '<i>';
1546  $state .= 'i';
1547  }
1548  } elseif ( $thislen == 3 ) {
1549  if ( $state === 'b' ) {
1550  $output .= '</b>';
1551  $state = '';
1552  } elseif ( $state === 'bi' ) {
1553  $output .= '</i></b><i>';
1554  $state = 'i';
1555  } elseif ( $state === 'ib' ) {
1556  $output .= '</b>';
1557  $state = 'i';
1558  } elseif ( $state === 'both' ) {
1559  $output .= '<i><b>' . $buffer . '</b>';
1560  $state = 'i';
1561  } else { // $state can be 'i' or ''
1562  $output .= '<b>';
1563  $state .= 'b';
1564  }
1565  } elseif ( $thislen == 5 ) {
1566  if ( $state === 'b' ) {
1567  $output .= '</b><i>';
1568  $state = 'i';
1569  } elseif ( $state === 'i' ) {
1570  $output .= '</i><b>';
1571  $state = 'b';
1572  } elseif ( $state === 'bi' ) {
1573  $output .= '</i></b>';
1574  $state = '';
1575  } elseif ( $state === 'ib' ) {
1576  $output .= '</b></i>';
1577  $state = '';
1578  } elseif ( $state === 'both' ) {
1579  $output .= '<i><b>' . $buffer . '</b></i>';
1580  $state = '';
1581  } else { // ($state == '')
1582  $buffer = '';
1583  $state = 'both';
1584  }
1585  }
1586  }
1587  $i++;
1588  }
1589  // Now close all remaining tags. Notice that the order is important.
1590  if ( $state === 'b' || $state === 'ib' ) {
1591  $output .= '</b>';
1592  }
1593  if ( $state === 'i' || $state === 'bi' || $state === 'ib' ) {
1594  $output .= '</i>';
1595  }
1596  if ( $state === 'bi' ) {
1597  $output .= '</b>';
1598  }
1599  // There might be lonely ''''', so make sure we have a buffer
1600  if ( $state === 'both' && $buffer ) {
1601  $output .= '<b><i>' . $buffer . '</i></b>';
1602  }
1603  return $output;
1604  }
1605 
1619  function replaceExternalLinks( $text ) {
1620  wfProfileIn( __METHOD__ );
1621 
1622  $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1623  if ( $bits === false ) {
1624  wfProfileOut( __METHOD__ );
1625  throw new MWException( "PCRE needs to be compiled with --enable-unicode-properties in order for MediaWiki to function" );
1626  }
1627  $s = array_shift( $bits );
1628 
1629  $i = 0;
1630  while ( $i < count( $bits ) ) {
1631  $url = $bits[$i++];
1632  $i++; // protocol
1633  $text = $bits[$i++];
1634  $trail = $bits[$i++];
1635 
1636  # The characters '<' and '>' (which were escaped by
1637  # removeHTMLtags()) should not be included in
1638  # URLs, per RFC 2396.
1639  $m2 = array();
1640  if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
1641  $text = substr( $url, $m2[0][1] ) . ' ' . $text;
1642  $url = substr( $url, 0, $m2[0][1] );
1643  }
1644 
1645  # If the link text is an image URL, replace it with an <img> tag
1646  # This happened by accident in the original parser, but some people used it extensively
1647  $img = $this->maybeMakeExternalImage( $text );
1648  if ( $img !== false ) {
1649  $text = $img;
1650  }
1651 
1652  $dtrail = '';
1653 
1654  # Set linktype for CSS - if URL==text, link is essentially free
1655  $linktype = ( $text === $url ) ? 'free' : 'text';
1656 
1657  # No link text, e.g. [http://domain.tld/some.link]
1658  if ( $text == '' ) {
1659  # Autonumber
1660  $langObj = $this->getTargetLanguage();
1661  $text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
1662  $linktype = 'autonumber';
1663  } else {
1664  # Have link text, e.g. [http://domain.tld/some.link text]s
1665  # Check for trail
1666  list( $dtrail, $trail ) = Linker::splitTrail( $trail );
1667  }
1668 
1669  $text = $this->getConverterLanguage()->markNoConversion( $text );
1670 
1671  $url = Sanitizer::cleanUrl( $url );
1672 
1673  # Use the encoded URL
1674  # This means that users can paste URLs directly into the text
1675  # Funny characters like ö aren't valid in URLs anyway
1676  # This was changed in August 2004
1677  $s .= Linker::makeExternalLink( $url, $text, false, $linktype,
1678  $this->getExternalLinkAttribs( $url ) ) . $dtrail . $trail;
1679 
1680  # Register link in the output object.
1681  $this->mOutput->addExternalLink( $url );
1682  }
1683 
1684  wfProfileOut( __METHOD__ );
1685  return $s;
1686  }
1687 
1697  public static function getExternalLinkRel( $url = false, $title = null ) {
1698  global $wgNoFollowLinks, $wgNoFollowNsExceptions, $wgNoFollowDomainExceptions;
1699  $ns = $title ? $title->getNamespace() : false;
1700  if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions )
1701  && !wfMatchesDomainList( $url, $wgNoFollowDomainExceptions )
1702  ) {
1703  return 'nofollow';
1704  }
1705  return null;
1706  }
1707 
1718  function getExternalLinkAttribs( $url = false ) {
1719  $attribs = array();
1720  $rel = self::getExternalLinkRel( $url, $this->mTitle );
1721 
1722  $target = $this->mOptions->getExternalLinkTarget();
1723  if ( $target ) {
1724  $attribs['target'] = $target;
1725  if ( !in_array( $target, array( '_self', '_parent', '_top' ) ) ) {
1726  // T133507. New windows can navigate parent cross-origin.
1727  // Including noreferrer due to lacking browser
1728  // support of noopener. Eventually noreferrer should be removed.
1729  if ( $rel !== '' ) {
1730  $rel .= ' ';
1731  }
1732  $rel .= 'noreferrer noopener';
1733  }
1734  }
1735  $attribs['rel'] = $rel;
1736  return $attribs;
1737  }
1738 
1750  static function replaceUnusualEscapes( $url ) {
1751  return preg_replace_callback( '/%[0-9A-Fa-f]{2}/',
1752  array( __CLASS__, 'replaceUnusualEscapesCallback' ), $url );
1753  }
1754 
1763  private static function replaceUnusualEscapesCallback( $matches ) {
1764  $char = urldecode( $matches[0] );
1765  $ord = ord( $char );
1766  # Is it an unsafe or HTTP reserved character according to RFC 1738?
1767  if ( $ord > 32 && $ord < 127 && strpos( '<>"#{}|\^~[]`;/?', $char ) === false ) {
1768  # No, shouldn't be escaped
1769  return $char;
1770  } else {
1771  # Yes, leave it escaped
1772  return $matches[0];
1773  }
1774  }
1775 
1785  function maybeMakeExternalImage( $url ) {
1786  $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
1787  $imagesexception = !empty( $imagesfrom );
1788  $text = false;
1789  # $imagesfrom could be either a single string or an array of strings, parse out the latter
1790  if ( $imagesexception && is_array( $imagesfrom ) ) {
1791  $imagematch = false;
1792  foreach ( $imagesfrom as $match ) {
1793  if ( strpos( $url, $match ) === 0 ) {
1794  $imagematch = true;
1795  break;
1796  }
1797  }
1798  } elseif ( $imagesexception ) {
1799  $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
1800  } else {
1801  $imagematch = false;
1802  }
1803  if ( $this->mOptions->getAllowExternalImages()
1804  || ( $imagesexception && $imagematch ) ) {
1805  if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
1806  # Image found
1807  $text = Linker::makeExternalImage( $url );
1808  }
1809  }
1810  if ( !$text && $this->mOptions->getEnableImageWhitelist()
1811  && preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
1812  $whitelist = explode( "\n", wfMessage( 'external_image_whitelist' )->inContentLanguage()->text() );
1813  foreach ( $whitelist as $entry ) {
1814  # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
1815  if ( strpos( $entry, '#' ) === 0 || $entry === '' ) {
1816  continue;
1817  }
1818  if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
1819  # Image matches a whitelist entry
1820  $text = Linker::makeExternalImage( $url );
1821  break;
1822  }
1823  }
1824  }
1825  return $text;
1826  }
1827 
1837  function replaceInternalLinks( $s ) {
1838  $this->mLinkHolders->merge( $this->replaceInternalLinks2( $s ) );
1839  return $s;
1840  }
1841 
1850  function replaceInternalLinks2( &$s ) {
1851  wfProfileIn( __METHOD__ );
1852 
1853  wfProfileIn( __METHOD__ . '-setup' );
1854  static $tc = false, $e1, $e1_img;
1855  # the % is needed to support urlencoded titles as well
1856  if ( !$tc ) {
1857  $tc = Title::legalChars() . '#%';
1858  # Match a link having the form [[namespace:link|alternate]]trail
1859  $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
1860  # Match cases where there is no "]]", which might still be images
1861  $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
1862  }
1863 
1864  $holders = new LinkHolderArray( $this );
1865 
1866  # split the entire text string on occurrences of [[
1867  $a = StringUtils::explode( '[[', ' ' . $s );
1868  # get the first element (all text up to first [[), and remove the space we added
1869  $s = $a->current();
1870  $a->next();
1871  $line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
1872  $s = substr( $s, 1 );
1873 
1874  $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
1875  $e2 = null;
1876  if ( $useLinkPrefixExtension ) {
1877  # Match the end of a line for a word that's not followed by whitespace,
1878  # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
1880  $charset = $wgContLang->linkPrefixCharset();
1881  $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
1882  }
1883 
1884  if ( is_null( $this->mTitle ) ) {
1885  wfProfileOut( __METHOD__ . '-setup' );
1886  wfProfileOut( __METHOD__ );
1887  throw new MWException( __METHOD__ . ": \$this->mTitle is null\n" );
1888  }
1889  $nottalk = !$this->mTitle->isTalkPage();
1890 
1891  if ( $useLinkPrefixExtension ) {
1892  $m = array();
1893  if ( preg_match( $e2, $s, $m ) ) {
1894  $first_prefix = $m[2];
1895  } else {
1896  $first_prefix = false;
1897  }
1898  } else {
1899  $prefix = '';
1900  }
1901 
1902  $useSubpages = $this->areSubpagesAllowed();
1903  wfProfileOut( __METHOD__ . '-setup' );
1904 
1905  # Loop for each link
1906  for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) {
1907  # Check for excessive memory usage
1908  if ( $holders->isBig() ) {
1909  # Too big
1910  # Do the existence check, replace the link holders and clear the array
1911  $holders->replace( $s );
1912  $holders->clear();
1913  }
1914 
1915  if ( $useLinkPrefixExtension ) {
1916  wfProfileIn( __METHOD__ . '-prefixhandling' );
1917  if ( preg_match( $e2, $s, $m ) ) {
1918  $prefix = $m[2];
1919  $s = $m[1];
1920  } else {
1921  $prefix = '';
1922  }
1923  # first link
1924  if ( $first_prefix ) {
1925  $prefix = $first_prefix;
1926  $first_prefix = false;
1927  }
1928  wfProfileOut( __METHOD__ . '-prefixhandling' );
1929  }
1930 
1931  $might_be_img = false;
1932 
1933  wfProfileIn( __METHOD__ . "-e1" );
1934  if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
1935  $text = $m[2];
1936  # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
1937  # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
1938  # the real problem is with the $e1 regex
1939  # See bug 1300.
1940  #
1941  # Still some problems for cases where the ] is meant to be outside punctuation,
1942  # and no image is in sight. See bug 2095.
1943  #
1944  if ( $text !== ''
1945  && substr( $m[3], 0, 1 ) === ']'
1946  && strpos( $text, '[' ) !== false
1947  ) {
1948  $text .= ']'; # so that replaceExternalLinks($text) works later
1949  $m[3] = substr( $m[3], 1 );
1950  }
1951  # fix up urlencoded title texts
1952  if ( strpos( $m[1], '%' ) !== false ) {
1953  # Should anchors '#' also be rejected?
1954  $m[1] = str_replace( array( '<', '>' ), array( '&lt;', '&gt;' ), rawurldecode( $m[1] ) );
1955  }
1956  $trail = $m[3];
1957  } elseif ( preg_match( $e1_img, $line, $m ) ) { # Invalid, but might be an image with a link in its caption
1958  $might_be_img = true;
1959  $text = $m[2];
1960  if ( strpos( $m[1], '%' ) !== false ) {
1961  $m[1] = str_replace( array( '<', '>' ), array( '&lt;', '&gt;' ), rawurldecode( $m[1] ) );
1962  }
1963  $trail = "";
1964  } else { # Invalid form; output directly
1965  $s .= $prefix . '[[' . $line;
1966  wfProfileOut( __METHOD__ . "-e1" );
1967  continue;
1968  }
1969  wfProfileOut( __METHOD__ . "-e1" );
1970  wfProfileIn( __METHOD__ . "-misc" );
1971 
1972  # Don't allow internal links to pages containing
1973  # PROTO: where PROTO is a valid URL protocol; these
1974  # should be external links.
1975  if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $m[1] ) ) {
1976  $s .= $prefix . '[[' . $line;
1977  wfProfileOut( __METHOD__ . "-misc" );
1978  continue;
1979  }
1980 
1981  # Make subpage if necessary
1982  if ( $useSubpages ) {
1983  $link = $this->maybeDoSubpageLink( $m[1], $text );
1984  } else {
1985  $link = $m[1];
1986  }
1987 
1988  $noforce = ( substr( $m[1], 0, 1 ) !== ':' );
1989  if ( !$noforce ) {
1990  # Strip off leading ':'
1991  $link = substr( $link, 1 );
1992  }
1993 
1994  wfProfileOut( __METHOD__ . "-misc" );
1995  wfProfileIn( __METHOD__ . "-title" );
1996  $nt = Title::newFromText( $this->mStripState->unstripNoWiki( $link ) );
1997  if ( $nt === null ) {
1998  $s .= $prefix . '[[' . $line;
1999  wfProfileOut( __METHOD__ . "-title" );
2000  continue;
2001  }
2002 
2003  $ns = $nt->getNamespace();
2004  $iw = $nt->getInterwiki();
2005  wfProfileOut( __METHOD__ . "-title" );
2006 
2007  if ( $might_be_img ) { # if this is actually an invalid link
2008  wfProfileIn( __METHOD__ . "-might_be_img" );
2009  if ( $ns == NS_FILE && $noforce ) { # but might be an image
2010  $found = false;
2011  while ( true ) {
2012  # look at the next 'line' to see if we can close it there
2013  $a->next();
2014  $next_line = $a->current();
2015  if ( $next_line === false || $next_line === null ) {
2016  break;
2017  }
2018  $m = explode( ']]', $next_line, 3 );
2019  if ( count( $m ) == 3 ) {
2020  # the first ]] closes the inner link, the second the image
2021  $found = true;
2022  $text .= "[[{$m[0]}]]{$m[1]}";
2023  $trail = $m[2];
2024  break;
2025  } elseif ( count( $m ) == 2 ) {
2026  # if there's exactly one ]] that's fine, we'll keep looking
2027  $text .= "[[{$m[0]}]]{$m[1]}";
2028  } else {
2029  # if $next_line is invalid too, we need look no further
2030  $text .= '[[' . $next_line;
2031  break;
2032  }
2033  }
2034  if ( !$found ) {
2035  # we couldn't find the end of this imageLink, so output it raw
2036  # but don't ignore what might be perfectly normal links in the text we've examined
2037  $holders->merge( $this->replaceInternalLinks2( $text ) );
2038  $s .= "{$prefix}[[$link|$text";
2039  # note: no $trail, because without an end, there *is* no trail
2040  wfProfileOut( __METHOD__ . "-might_be_img" );
2041  continue;
2042  }
2043  } else { # it's not an image, so output it raw
2044  $s .= "{$prefix}[[$link|$text";
2045  # note: no $trail, because without an end, there *is* no trail
2046  wfProfileOut( __METHOD__ . "-might_be_img" );
2047  continue;
2048  }
2049  wfProfileOut( __METHOD__ . "-might_be_img" );
2050  }
2051 
2052  $wasblank = ( $text == '' );
2053  if ( $wasblank ) {
2054  $text = $link;
2055  } else {
2056  # Bug 4598 madness. Handle the quotes only if they come from the alternate part
2057  # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2058  # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2059  # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2060  $text = $this->doQuotes( $text );
2061  }
2062 
2063  # Link not escaped by : , create the various objects
2064  if ( $noforce ) {
2065  # Interwikis
2066  wfProfileIn( __METHOD__ . "-interwiki" );
2067  if ( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && Language::fetchLanguageName( $iw, null, 'mw' ) ) {
2068  // XXX: the above check prevents links to sites with identifiers that are not language codes
2069 
2070  # Bug 24502: filter duplicates
2071  if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
2072  $this->mLangLinkLanguages[$iw] = true;
2073  $this->mOutput->addLanguageLink( $nt->getFullText() );
2074  }
2075 
2076  $s = rtrim( $s . $prefix );
2077  $s .= trim( $trail, "\n" ) == '' ? '': $prefix . $trail;
2078  wfProfileOut( __METHOD__ . "-interwiki" );
2079  continue;
2080  }
2081  wfProfileOut( __METHOD__ . "-interwiki" );
2082 
2083  if ( $ns == NS_FILE ) {
2084  wfProfileIn( __METHOD__ . "-image" );
2085  if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
2086  if ( $wasblank ) {
2087  # if no parameters were passed, $text
2088  # becomes something like "File:Foo.png",
2089  # which we don't want to pass on to the
2090  # image generator
2091  $text = '';
2092  } else {
2093  # recursively parse links inside the image caption
2094  # actually, this will parse them in any other parameters, too,
2095  # but it might be hard to fix that, and it doesn't matter ATM
2096  $text = $this->replaceExternalLinks( $text );
2097  $holders->merge( $this->replaceInternalLinks2( $text ) );
2098  }
2099  # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
2100  $s .= $prefix . $this->armorLinks(
2101  $this->makeImage( $nt, $text, $holders ) ) . $trail;
2102  } else {
2103  $s .= $prefix . $trail;
2104  }
2105  wfProfileOut( __METHOD__ . "-image" );
2106  continue;
2107  }
2108 
2109  if ( $ns == NS_CATEGORY ) {
2110  wfProfileIn( __METHOD__ . "-category" );
2111  $s = rtrim( $s . "\n" ); # bug 87
2112 
2113  if ( $wasblank ) {
2114  $sortkey = $this->getDefaultSort();
2115  } else {
2116  $sortkey = $text;
2117  }
2118  $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2119  $sortkey = str_replace( "\n", '', $sortkey );
2120  $sortkey = $this->getConverterLanguage()->convertCategoryKey( $sortkey );
2121  $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2122 
2126  $s .= trim( $prefix . $trail, "\n" ) == '' ? '' : $prefix . $trail;
2127 
2128  wfProfileOut( __METHOD__ . "-category" );
2129  continue;
2130  }
2131  }
2132 
2133  # Self-link checking. For some languages, variants of the title are checked in
2134  # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2135  # for linking to a different variant.
2136  if ( $ns != NS_SPECIAL && $nt->equals( $this->mTitle ) && !$nt->hasFragment() ) {
2137  $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail );
2138  continue;
2139  }
2140 
2141  # NS_MEDIA is a pseudo-namespace for linking directly to a file
2142  # @todo FIXME: Should do batch file existence checks, see comment below
2143  if ( $ns == NS_MEDIA ) {
2144  wfProfileIn( __METHOD__ . "-media" );
2145  # Give extensions a chance to select the file revision for us
2146  $options = array();
2147  $descQuery = false;
2148  wfRunHooks( 'BeforeParserFetchFileAndTitle',
2149  array( $this, $nt, &$options, &$descQuery ) );
2150  # Fetch and register the file (file title may be different via hooks)
2151  list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $options );
2152  # Cloak with NOPARSE to avoid replacement in replaceExternalLinks
2153  $s .= $prefix . $this->armorLinks(
2154  Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2155  wfProfileOut( __METHOD__ . "-media" );
2156  continue;
2157  }
2158 
2159  wfProfileIn( __METHOD__ . "-always_known" );
2160  # Some titles, such as valid special pages or files in foreign repos, should
2161  # be shown as bluelinks even though they're not included in the page table
2162  #
2163  # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2164  # batch file existence checks for NS_FILE and NS_MEDIA
2165  if ( $iw == '' && $nt->isAlwaysKnown() ) {
2166  $this->mOutput->addLink( $nt );
2167  $s .= $this->makeKnownLinkHolder( $nt, $text, array(), $trail, $prefix );
2168  } else {
2169  # Links will be added to the output link list after checking
2170  $s .= $holders->makeHolder( $nt, $text, array(), $trail, $prefix );
2171  }
2172  wfProfileOut( __METHOD__ . "-always_known" );
2173  }
2174  wfProfileOut( __METHOD__ );
2175  return $holders;
2176  }
2177 
2192  function makeKnownLinkHolder( $nt, $text = '', $query = array(), $trail = '', $prefix = '' ) {
2193  list( $inside, $trail ) = Linker::splitTrail( $trail );
2194 
2195  if ( is_string( $query ) ) {
2196  $query = wfCgiToArray( $query );
2197  }
2198  if ( $text == '' ) {
2199  $text = htmlspecialchars( $nt->getPrefixedText() );
2200  }
2201 
2202  $link = Linker::linkKnown( $nt, "$prefix$text$inside", array(), $query );
2203 
2204  return $this->armorLinks( $link ) . $trail;
2205  }
2206 
2217  function armorLinks( $text ) {
2218  return preg_replace( '/\b((?i)' . $this->mUrlProtocols . ')/',
2219  "{$this->mUniqPrefix}NOPARSE$1", $text );
2220  }
2221 
2226  function areSubpagesAllowed() {
2227  # Some namespaces don't allow subpages
2228  return MWNamespace::hasSubpages( $this->mTitle->getNamespace() );
2229  }
2230 
2239  function maybeDoSubpageLink( $target, &$text ) {
2240  return Linker::normalizeSubpageLink( $this->mTitle, $target, $text );
2241  }
2242 
2249  function closeParagraph() {
2250  $result = '';
2251  if ( $this->mLastSection != '' ) {
2252  $result = '</' . $this->mLastSection . ">\n";
2253  }
2254  $this->mInPre = false;
2255  $this->mLastSection = '';
2256  return $result;
2257  }
2258 
2269  function getCommon( $st1, $st2 ) {
2270  $fl = strlen( $st1 );
2271  $shorter = strlen( $st2 );
2272  if ( $fl < $shorter ) {
2273  $shorter = $fl;
2274  }
2275 
2276  for ( $i = 0; $i < $shorter; ++$i ) {
2277  if ( $st1[$i] != $st2[$i] ) {
2278  break;
2279  }
2280  }
2281  return $i;
2282  }
2283 
2293  function openList( $char ) {
2294  $result = $this->closeParagraph();
2295 
2296  if ( '*' === $char ) {
2297  $result .= "<ul>\n<li>";
2298  } elseif ( '#' === $char ) {
2299  $result .= "<ol>\n<li>";
2300  } elseif ( ':' === $char ) {
2301  $result .= "<dl>\n<dd>";
2302  } elseif ( ';' === $char ) {
2303  $result .= "<dl>\n<dt>";
2304  $this->mDTopen = true;
2305  } else {
2306  $result = '<!-- ERR 1 -->';
2307  }
2308 
2309  return $result;
2310  }
2311 
2319  function nextItem( $char ) {
2320  if ( '*' === $char || '#' === $char ) {
2321  return "</li>\n<li>";
2322  } elseif ( ':' === $char || ';' === $char ) {
2323  $close = "</dd>\n";
2324  if ( $this->mDTopen ) {
2325  $close = "</dt>\n";
2326  }
2327  if ( ';' === $char ) {
2328  $this->mDTopen = true;
2329  return $close . '<dt>';
2330  } else {
2331  $this->mDTopen = false;
2332  return $close . '<dd>';
2333  }
2334  }
2335  return '<!-- ERR 2 -->';
2336  }
2337 
2345  function closeList( $char ) {
2346  if ( '*' === $char ) {
2347  $text = "</li>\n</ul>";
2348  } elseif ( '#' === $char ) {
2349  $text = "</li>\n</ol>";
2350  } elseif ( ':' === $char ) {
2351  if ( $this->mDTopen ) {
2352  $this->mDTopen = false;
2353  $text = "</dt>\n</dl>";
2354  } else {
2355  $text = "</dd>\n</dl>";
2356  }
2357  } else {
2358  return '<!-- ERR 3 -->';
2359  }
2360  return $text . "\n";
2361  }
2372  function doBlockLevels( $text, $linestart ) {
2373  wfProfileIn( __METHOD__ );
2374 
2375  # Parsing through the text line by line. The main thing
2376  # happening here is handling of block-level elements p, pre,
2377  # and making lists from lines starting with * # : etc.
2378  #
2379  $textLines = StringUtils::explode( "\n", $text );
2380 
2381  $lastPrefix = $output = '';
2382  $this->mDTopen = $inBlockElem = false;
2383  $prefixLength = 0;
2384  $paragraphStack = false;
2385  $inBlockquote = false;
2386 
2387  foreach ( $textLines as $oLine ) {
2388  # Fix up $linestart
2389  if ( !$linestart ) {
2390  $output .= $oLine;
2391  $linestart = true;
2392  continue;
2393  }
2394  # * = ul
2395  # # = ol
2396  # ; = dt
2397  # : = dd
2398 
2399  $lastPrefixLength = strlen( $lastPrefix );
2400  $preCloseMatch = preg_match( '/<\\/pre/i', $oLine );
2401  $preOpenMatch = preg_match( '/<pre/i', $oLine );
2402  # If not in a <pre> element, scan for and figure out what prefixes are there.
2403  if ( !$this->mInPre ) {
2404  # Multiple prefixes may abut each other for nested lists.
2405  $prefixLength = strspn( $oLine, '*#:;' );
2406  $prefix = substr( $oLine, 0, $prefixLength );
2407 
2408  # eh?
2409  # ; and : are both from definition-lists, so they're equivalent
2410  # for the purposes of determining whether or not we need to open/close
2411  # elements.
2412  $prefix2 = str_replace( ';', ':', $prefix );
2413  $t = substr( $oLine, $prefixLength );
2414  $this->mInPre = (bool)$preOpenMatch;
2415  } else {
2416  # Don't interpret any other prefixes in preformatted text
2417  $prefixLength = 0;
2418  $prefix = $prefix2 = '';
2419  $t = $oLine;
2420  }
2421 
2422  # List generation
2423  if ( $prefixLength && $lastPrefix === $prefix2 ) {
2424  # Same as the last item, so no need to deal with nesting or opening stuff
2425  $output .= $this->nextItem( substr( $prefix, -1 ) );
2426  $paragraphStack = false;
2427 
2428  if ( substr( $prefix, -1 ) === ';' ) {
2429  # The one nasty exception: definition lists work like this:
2430  # ; title : definition text
2431  # So we check for : in the remainder text to split up the
2432  # title and definition, without b0rking links.
2433  $term = $t2 = '';
2434  if ( $this->findColonNoLinks( $t, $term, $t2 ) !== false ) {
2435  $t = $t2;
2436  $output .= $term . $this->nextItem( ':' );
2437  }
2438  }
2439  } elseif ( $prefixLength || $lastPrefixLength ) {
2440  # We need to open or close prefixes, or both.
2441 
2442  # Either open or close a level...
2443  $commonPrefixLength = $this->getCommon( $prefix, $lastPrefix );
2444  $paragraphStack = false;
2445 
2446  # Close all the prefixes which aren't shared.
2447  while ( $commonPrefixLength < $lastPrefixLength ) {
2448  $output .= $this->closeList( $lastPrefix[$lastPrefixLength - 1] );
2449  --$lastPrefixLength;
2450  }
2451 
2452  # Continue the current prefix if appropriate.
2453  if ( $prefixLength <= $commonPrefixLength && $commonPrefixLength > 0 ) {
2454  $output .= $this->nextItem( $prefix[$commonPrefixLength - 1] );
2455  }
2456 
2457  # Open prefixes where appropriate.
2458  while ( $prefixLength > $commonPrefixLength ) {
2459  $char = substr( $prefix, $commonPrefixLength, 1 );
2460  $output .= $this->openList( $char );
2461 
2462  if ( ';' === $char ) {
2463  # @todo FIXME: This is dupe of code above
2464  if ( $this->findColonNoLinks( $t, $term, $t2 ) !== false ) {
2465  $t = $t2;
2466  $output .= $term . $this->nextItem( ':' );
2467  }
2468  }
2469  ++$commonPrefixLength;
2470  }
2471  $lastPrefix = $prefix2;
2472  }
2473 
2474  # If we have no prefixes, go to paragraph mode.
2475  if ( 0 == $prefixLength ) {
2476  wfProfileIn( __METHOD__ . "-paragraph" );
2477  # No prefix (not in list)--go to paragraph mode
2478  # XXX: use a stack for nestable elements like span, table and div
2479  $openmatch = preg_match( '/(?:<table|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<dl|<li|<\\/tr|<\\/td|<\\/th)/iS', $t );
2480  $closematch = preg_match(
2481  '/(?:<\\/table|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|' .
2482  '<td|<th|<\\/?blockquote|<\\/?div|<hr|<\\/pre|<\\/p|<\\/mw:|' . $this->mUniqPrefix . '-pre|<\\/li|<\\/ul|<\\/ol|<\\/dl|<\\/?center)/iS', $t );
2483  if ( $openmatch or $closematch ) {
2484  $paragraphStack = false;
2485  # TODO bug 5718: paragraph closed
2486  $output .= $this->closeParagraph();
2487  if ( $preOpenMatch and !$preCloseMatch ) {
2488  $this->mInPre = true;
2489  }
2490  $bqOffset = 0;
2491  while ( preg_match( '/<(\\/?)blockquote[\s>]/i', $t, $bqMatch, PREG_OFFSET_CAPTURE, $bqOffset ) ) {
2492  $inBlockquote = !$bqMatch[1][0]; // is this a close tag?
2493  $bqOffset = $bqMatch[0][1] + strlen( $bqMatch[0][0] );
2494  }
2495  $inBlockElem = !$closematch;
2496  } elseif ( !$inBlockElem && !$this->mInPre ) {
2497  if ( ' ' == substr( $t, 0, 1 ) and ( $this->mLastSection === 'pre' || trim( $t ) != '' ) and !$inBlockquote ) {
2498  # pre
2499  if ( $this->mLastSection !== 'pre' ) {
2500  $paragraphStack = false;
2501  $output .= $this->closeParagraph() . '<pre>';
2502  $this->mLastSection = 'pre';
2503  }
2504  $t = substr( $t, 1 );
2505  } else {
2506  # paragraph
2507  if ( trim( $t ) === '' ) {
2508  if ( $paragraphStack ) {
2509  $output .= $paragraphStack . '<br />';
2510  $paragraphStack = false;
2511  $this->mLastSection = 'p';
2512  } else {
2513  if ( $this->mLastSection !== 'p' ) {
2514  $output .= $this->closeParagraph();
2515  $this->mLastSection = '';
2516  $paragraphStack = '<p>';
2517  } else {
2518  $paragraphStack = '</p><p>';
2519  }
2520  }
2521  } else {
2522  if ( $paragraphStack ) {
2523  $output .= $paragraphStack;
2524  $paragraphStack = false;
2525  $this->mLastSection = 'p';
2526  } elseif ( $this->mLastSection !== 'p' ) {
2527  $output .= $this->closeParagraph() . '<p>';
2528  $this->mLastSection = 'p';
2529  }
2530  }
2531  }
2532  }
2533  wfProfileOut( __METHOD__ . "-paragraph" );
2534  }
2535  # somewhere above we forget to get out of pre block (bug 785)
2536  if ( $preCloseMatch && $this->mInPre ) {
2537  $this->mInPre = false;
2538  }
2539  if ( $paragraphStack === false ) {
2540  $output .= $t . "\n";
2541  }
2542  }
2543  while ( $prefixLength ) {
2544  $output .= $this->closeList( $prefix2[$prefixLength - 1] );
2545  --$prefixLength;
2546  }
2547  if ( $this->mLastSection != '' ) {
2548  $output .= '</' . $this->mLastSection . '>';
2549  $this->mLastSection = '';
2550  }
2551 
2552  wfProfileOut( __METHOD__ );
2553  return $output;
2554  }
2555 
2566  function findColonNoLinks( $str, &$before, &$after ) {
2567  wfProfileIn( __METHOD__ );
2568 
2569  $pos = strpos( $str, ':' );
2570  if ( $pos === false ) {
2571  # Nothing to find!
2572  wfProfileOut( __METHOD__ );
2573  return false;
2574  }
2575 
2576  $lt = strpos( $str, '<' );
2577  if ( $lt === false || $lt > $pos ) {
2578  # Easy; no tag nesting to worry about
2579  $before = substr( $str, 0, $pos );
2580  $after = substr( $str, $pos + 1 );
2581  wfProfileOut( __METHOD__ );
2582  return $pos;
2583  }
2584 
2585  # Ugly state machine to walk through avoiding tags.
2586  $state = self::COLON_STATE_TEXT;
2587  $stack = 0;
2588  $len = strlen( $str );
2589  for ( $i = 0; $i < $len; $i++ ) {
2590  $c = $str[$i];
2591 
2592  switch ( $state ) {
2593  # (Using the number is a performance hack for common cases)
2594  case 0: # self::COLON_STATE_TEXT:
2595  switch ( $c ) {
2596  case "<":
2597  # Could be either a <start> tag or an </end> tag
2598  $state = self::COLON_STATE_TAGSTART;
2599  break;
2600  case ":":
2601  if ( $stack == 0 ) {
2602  # We found it!
2603  $before = substr( $str, 0, $i );
2604  $after = substr( $str, $i + 1 );
2605  wfProfileOut( __METHOD__ );
2606  return $i;
2607  }
2608  # Embedded in a tag; don't break it.
2609  break;
2610  default:
2611  # Skip ahead looking for something interesting
2612  $colon = strpos( $str, ':', $i );
2613  if ( $colon === false ) {
2614  # Nothing else interesting
2615  wfProfileOut( __METHOD__ );
2616  return false;
2617  }
2618  $lt = strpos( $str, '<', $i );
2619  if ( $stack === 0 ) {
2620  if ( $lt === false || $colon < $lt ) {
2621  # We found it!
2622  $before = substr( $str, 0, $colon );
2623  $after = substr( $str, $colon + 1 );
2624  wfProfileOut( __METHOD__ );
2625  return $i;
2626  }
2627  }
2628  if ( $lt === false ) {
2629  # Nothing else interesting to find; abort!
2630  # We're nested, but there's no close tags left. Abort!
2631  break 2;
2632  }
2633  # Skip ahead to next tag start
2634  $i = $lt;
2635  $state = self::COLON_STATE_TAGSTART;
2636  }
2637  break;
2638  case 1: # self::COLON_STATE_TAG:
2639  # In a <tag>
2640  switch ( $c ) {
2641  case ">":
2642  $stack++;
2643  $state = self::COLON_STATE_TEXT;
2644  break;
2645  case "/":
2646  # Slash may be followed by >?
2647  $state = self::COLON_STATE_TAGSLASH;
2648  break;
2649  default:
2650  # ignore
2651  }
2652  break;
2653  case 2: # self::COLON_STATE_TAGSTART:
2654  switch ( $c ) {
2655  case "/":
2656  $state = self::COLON_STATE_CLOSETAG;
2657  break;
2658  case "!":
2659  $state = self::COLON_STATE_COMMENT;
2660  break;
2661  case ">":
2662  # Illegal early close? This shouldn't happen D:
2663  $state = self::COLON_STATE_TEXT;
2664  break;
2665  default:
2666  $state = self::COLON_STATE_TAG;
2667  }
2668  break;
2669  case 3: # self::COLON_STATE_CLOSETAG:
2670  # In a </tag>
2671  if ( $c === ">" ) {
2672  $stack--;
2673  if ( $stack < 0 ) {
2674  wfDebug( __METHOD__ . ": Invalid input; too many close tags\n" );
2675  wfProfileOut( __METHOD__ );
2676  return false;
2677  }
2678  $state = self::COLON_STATE_TEXT;
2679  }
2680  break;
2681  case self::COLON_STATE_TAGSLASH:
2682  if ( $c === ">" ) {
2683  # Yes, a self-closed tag <blah/>
2684  $state = self::COLON_STATE_TEXT;
2685  } else {
2686  # Probably we're jumping the gun, and this is an attribute
2687  $state = self::COLON_STATE_TAG;
2688  }
2689  break;
2690  case 5: # self::COLON_STATE_COMMENT:
2691  if ( $c === "-" ) {
2692  $state = self::COLON_STATE_COMMENTDASH;
2693  }
2694  break;
2695  case self::COLON_STATE_COMMENTDASH:
2696  if ( $c === "-" ) {
2697  $state = self::COLON_STATE_COMMENTDASHDASH;
2698  } else {
2699  $state = self::COLON_STATE_COMMENT;
2700  }
2701  break;
2702  case self::COLON_STATE_COMMENTDASHDASH:
2703  if ( $c === ">" ) {
2704  $state = self::COLON_STATE_TEXT;
2705  } else {
2706  $state = self::COLON_STATE_COMMENT;
2707  }
2708  break;
2709  default:
2710  wfProfileOut( __METHOD__ );
2711  throw new MWException( "State machine error in " . __METHOD__ );
2712  }
2713  }
2714  if ( $stack > 0 ) {
2715  wfDebug( __METHOD__ . ": Invalid input; not enough close tags (stack $stack, state $state)\n" );
2716  wfProfileOut( __METHOD__ );
2717  return false;
2718  }
2719  wfProfileOut( __METHOD__ );
2720  return false;
2721  }
2722 
2734  function getVariableValue( $index, $frame = false ) {
2735  global $wgContLang, $wgSitename, $wgServer;
2736  global $wgArticlePath, $wgScriptPath, $wgStylePath;
2737 
2738  if ( is_null( $this->mTitle ) ) {
2739  // If no title set, bad things are going to happen
2740  // later. Title should always be set since this
2741  // should only be called in the middle of a parse
2742  // operation (but the unit-tests do funky stuff)
2743  throw new MWException( __METHOD__ . ' Should only be '
2744  . ' called while parsing (no title set)' );
2745  }
2746 
2751  if ( wfRunHooks( 'ParserGetVariableValueVarCache', array( &$this, &$this->mVarCache ) ) ) {
2752  if ( isset( $this->mVarCache[$index] ) ) {
2753  return $this->mVarCache[$index];
2754  }
2755  }
2756 
2757  $ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
2758  wfRunHooks( 'ParserGetVariableValueTs', array( &$this, &$ts ) );
2759 
2760  $pageLang = $this->getFunctionLang();
2761 
2762  switch ( $index ) {
2763  case 'currentmonth':
2764  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'm' ) );
2765  break;
2766  case 'currentmonth1':
2767  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2768  break;
2769  case 'currentmonthname':
2770  $value = $pageLang->getMonthName( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2771  break;
2772  case 'currentmonthnamegen':
2773  $value = $pageLang->getMonthNameGen( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2774  break;
2775  case 'currentmonthabbrev':
2776  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2777  break;
2778  case 'currentday':
2779  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'j' ) );
2780  break;
2781  case 'currentday2':
2782  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'd' ) );
2783  break;
2784  case 'localmonth':
2785  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'm' ) );
2786  break;
2787  case 'localmonth1':
2788  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2789  break;
2790  case 'localmonthname':
2791  $value = $pageLang->getMonthName( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2792  break;
2793  case 'localmonthnamegen':
2794  $value = $pageLang->getMonthNameGen( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2795  break;
2796  case 'localmonthabbrev':
2797  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2798  break;
2799  case 'localday':
2800  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'j' ) );
2801  break;
2802  case 'localday2':
2803  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'd' ) );
2804  break;
2805  case 'pagename':
2806  $value = wfEscapeWikiText( $this->mTitle->getText() );
2807  break;
2808  case 'pagenamee':
2809  $value = wfEscapeWikiText( $this->mTitle->getPartialURL() );
2810  break;
2811  case 'fullpagename':
2812  $value = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
2813  break;
2814  case 'fullpagenamee':
2815  $value = wfEscapeWikiText( $this->mTitle->getPrefixedURL() );
2816  break;
2817  case 'subpagename':
2818  $value = wfEscapeWikiText( $this->mTitle->getSubpageText() );
2819  break;
2820  case 'subpagenamee':
2821  $value = wfEscapeWikiText( $this->mTitle->getSubpageUrlForm() );
2822  break;
2823  case 'rootpagename':
2824  $value = wfEscapeWikiText( $this->mTitle->getRootText() );
2825  break;
2826  case 'rootpagenamee':
2827  $value = wfEscapeWikiText( wfUrlEncode( str_replace( ' ', '_', $this->mTitle->getRootText() ) ) );
2828  break;
2829  case 'basepagename':
2830  $value = wfEscapeWikiText( $this->mTitle->getBaseText() );
2831  break;
2832  case 'basepagenamee':
2833  $value = wfEscapeWikiText( wfUrlEncode( str_replace( ' ', '_', $this->mTitle->getBaseText() ) ) );
2834  break;
2835  case 'talkpagename':
2836  if ( $this->mTitle->canTalk() ) {
2837  $talkPage = $this->mTitle->getTalkPage();
2838  $value = wfEscapeWikiText( $talkPage->getPrefixedText() );
2839  } else {
2840  $value = '';
2841  }
2842  break;
2843  case 'talkpagenamee':
2844  if ( $this->mTitle->canTalk() ) {
2845  $talkPage = $this->mTitle->getTalkPage();
2846  $value = wfEscapeWikiText( $talkPage->getPrefixedURL() );
2847  } else {
2848  $value = '';
2849  }
2850  break;
2851  case 'subjectpagename':
2852  $subjPage = $this->mTitle->getSubjectPage();
2853  $value = wfEscapeWikiText( $subjPage->getPrefixedText() );
2854  break;
2855  case 'subjectpagenamee':
2856  $subjPage = $this->mTitle->getSubjectPage();
2857  $value = wfEscapeWikiText( $subjPage->getPrefixedURL() );
2858  break;
2859  case 'pageid': // requested in bug 23427
2860  $pageid = $this->getTitle()->getArticleID();
2861  if ( $pageid == 0 ) {
2862  # 0 means the page doesn't exist in the database,
2863  # which means the user is previewing a new page.
2864  # The vary-revision flag must be set, because the magic word
2865  # will have a different value once the page is saved.
2866  $this->mOutput->setFlag( 'vary-revision' );
2867  wfDebug( __METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision...\n" );
2868  }
2869  $value = $pageid ? $pageid : null;
2870  break;
2871  case 'revisionid':
2872  # Let the edit saving system know we should parse the page
2873  # *after* a revision ID has been assigned.
2874  $this->mOutput->setFlag( 'vary-revision' );
2875  wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision...\n" );
2876  $value = $this->mRevisionId;
2877  break;
2878  case 'revisionday':
2879  # Let the edit saving system know we should parse the page
2880  # *after* a revision ID has been assigned. This is for null edits.
2881  $this->mOutput->setFlag( 'vary-revision' );
2882  wfDebug( __METHOD__ . ": {{REVISIONDAY}} used, setting vary-revision...\n" );
2883  $value = intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
2884  break;
2885  case 'revisionday2':
2886  # Let the edit saving system know we should parse the page
2887  # *after* a revision ID has been assigned. This is for null edits.
2888  $this->mOutput->setFlag( 'vary-revision' );
2889  wfDebug( __METHOD__ . ": {{REVISIONDAY2}} used, setting vary-revision...\n" );
2890  $value = substr( $this->getRevisionTimestamp(), 6, 2 );
2891  break;
2892  case 'revisionmonth':
2893  # Let the edit saving system know we should parse the page
2894  # *after* a revision ID has been assigned. This is for null edits.
2895  $this->mOutput->setFlag( 'vary-revision' );
2896  wfDebug( __METHOD__ . ": {{REVISIONMONTH}} used, setting vary-revision...\n" );
2897  $value = substr( $this->getRevisionTimestamp(), 4, 2 );
2898  break;
2899  case 'revisionmonth1':
2900  # Let the edit saving system know we should parse the page
2901  # *after* a revision ID has been assigned. This is for null edits.
2902  $this->mOutput->setFlag( 'vary-revision' );
2903  wfDebug( __METHOD__ . ": {{REVISIONMONTH1}} used, setting vary-revision...\n" );
2904  $value = intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
2905  break;
2906  case 'revisionyear':
2907  # Let the edit saving system know we should parse the page
2908  # *after* a revision ID has been assigned. This is for null edits.
2909  $this->mOutput->setFlag( 'vary-revision' );
2910  wfDebug( __METHOD__ . ": {{REVISIONYEAR}} used, setting vary-revision...\n" );
2911  $value = substr( $this->getRevisionTimestamp(), 0, 4 );
2912  break;
2913  case 'revisiontimestamp':
2914  # Let the edit saving system know we should parse the page
2915  # *after* a revision ID has been assigned. This is for null edits.
2916  $this->mOutput->setFlag( 'vary-revision' );
2917  wfDebug( __METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" );
2918  $value = $this->getRevisionTimestamp();
2919  break;
2920  case 'revisionuser':
2921  # Let the edit saving system know we should parse the page
2922  # *after* a revision ID has been assigned. This is for null edits.
2923  $this->mOutput->setFlag( 'vary-revision' );
2924  wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-revision...\n" );
2925  $value = $this->getRevisionUser();
2926  break;
2927  case 'revisionsize':
2928  # Let the edit saving system know we should parse the page
2929  # *after* a revision ID has been assigned. This is for null edits.
2930  $this->mOutput->setFlag( 'vary-revision' );
2931  wfDebug( __METHOD__ . ": {{REVISIONSIZE}} used, setting vary-revision...\n" );
2932  $value = $this->getRevisionSize();
2933  break;
2934  case 'namespace':
2935  $value = str_replace( '_', ' ', $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2936  break;
2937  case 'namespacee':
2938  $value = wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2939  break;
2940  case 'namespacenumber':
2941  $value = $this->mTitle->getNamespace();
2942  break;
2943  case 'talkspace':
2944  $value = $this->mTitle->canTalk() ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() ) : '';
2945  break;
2946  case 'talkspacee':
2947  $value = $this->mTitle->canTalk() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
2948  break;
2949  case 'subjectspace':
2950  $value = str_replace( '_', ' ', $this->mTitle->getSubjectNsText() );
2951  break;
2952  case 'subjectspacee':
2953  $value = ( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
2954  break;
2955  case 'currentdayname':
2956  $value = $pageLang->getWeekdayName( (int)MWTimestamp::getInstance( $ts )->format( 'w' ) + 1 );
2957  break;
2958  case 'currentyear':
2959  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'Y' ), true );
2960  break;
2961  case 'currenttime':
2962  $value = $pageLang->time( wfTimestamp( TS_MW, $ts ), false, false );
2963  break;
2964  case 'currenthour':
2965  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'H' ), true );
2966  break;
2967  case 'currentweek':
2968  # @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
2969  # int to remove the padding
2970  $value = $pageLang->formatNum( (int)MWTimestamp::getInstance( $ts )->format( 'W' ) );
2971  break;
2972  case 'currentdow':
2973  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'w' ) );
2974  break;
2975  case 'localdayname':
2976  $value = $pageLang->getWeekdayName( (int)MWTimestamp::getLocalInstance( $ts )->format( 'w' ) + 1 );
2977  break;
2978  case 'localyear':
2979  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'Y' ), true );
2980  break;
2981  case 'localtime':
2982  $value = $pageLang->time( MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' ), false, false );
2983  break;
2984  case 'localhour':
2985  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'H' ), true );
2986  break;
2987  case 'localweek':
2988  # @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
2989  # int to remove the padding
2990  $value = $pageLang->formatNum( (int)MWTimestamp::getLocalInstance( $ts )->format( 'W' ) );
2991  break;
2992  case 'localdow':
2993  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'w' ) );
2994  break;
2995  case 'numberofarticles':
2996  $value = $pageLang->formatNum( SiteStats::articles() );
2997  break;
2998  case 'numberoffiles':
2999  $value = $pageLang->formatNum( SiteStats::images() );
3000  break;
3001  case 'numberofusers':
3002  $value = $pageLang->formatNum( SiteStats::users() );
3003  break;
3004  case 'numberofactiveusers':
3005  $value = $pageLang->formatNum( SiteStats::activeUsers() );
3006  break;
3007  case 'numberofpages':
3008  $value = $pageLang->formatNum( SiteStats::pages() );
3009  break;
3010  case 'numberofadmins':
3011  $value = $pageLang->formatNum( SiteStats::numberingroup( 'sysop' ) );
3012  break;
3013  case 'numberofedits':
3014  $value = $pageLang->formatNum( SiteStats::edits() );
3015  break;
3016  case 'numberofviews':
3017  global $wgDisableCounters;
3018  $value = !$wgDisableCounters ? $pageLang->formatNum( SiteStats::views() ) : '';
3019  break;
3020  case 'currenttimestamp':
3021  $value = wfTimestamp( TS_MW, $ts );
3022  break;
3023  case 'localtimestamp':
3024  $value = MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' );
3025  break;
3026  case 'currentversion':
3028  break;
3029  case 'articlepath':
3030  return $wgArticlePath;
3031  case 'sitename':
3032  return $wgSitename;
3033  case 'server':
3034  return $wgServer;
3035  case 'servername':
3036  $serverParts = wfParseUrl( $wgServer );
3037  return $serverParts && isset( $serverParts['host'] ) ? $serverParts['host'] : $wgServer;
3038  case 'scriptpath':
3039  return $wgScriptPath;
3040  case 'stylepath':
3041  return $wgStylePath;
3042  case 'directionmark':
3043  return $pageLang->getDirMark();
3044  case 'contentlanguage':
3045  global $wgLanguageCode;
3046  return $wgLanguageCode;
3047  case 'cascadingsources':
3049  break;
3050  default:
3051  $ret = null;
3052  wfRunHooks( 'ParserGetVariableValueSwitch', array( &$this, &$this->mVarCache, &$index, &$ret, &$frame ) );
3053  return $ret;
3054  }
3055 
3056  if ( $index ) {
3057  $this->mVarCache[$index] = $value;
3058  }
3059 
3060  return $value;
3061  }
3062 
3068  function initialiseVariables() {
3069  wfProfileIn( __METHOD__ );
3070  $variableIDs = MagicWord::getVariableIDs();
3071  $substIDs = MagicWord::getSubstIDs();
3072 
3073  $this->mVariables = new MagicWordArray( $variableIDs );
3074  $this->mSubstWords = new MagicWordArray( $substIDs );
3075  wfProfileOut( __METHOD__ );
3076  }
3077 
3100  function preprocessToDom( $text, $flags = 0 ) {
3101  $dom = $this->getPreprocessor()->preprocessToObj( $text, $flags );
3102  return $dom;
3103  }
3104 
3112  public static function splitWhitespace( $s ) {
3113  $ltrimmed = ltrim( $s );
3114  $w1 = substr( $s, 0, strlen( $s ) - strlen( $ltrimmed ) );
3115  $trimmed = rtrim( $ltrimmed );
3116  $diff = strlen( $ltrimmed ) - strlen( $trimmed );
3117  if ( $diff > 0 ) {
3118  $w2 = substr( $ltrimmed, -$diff );
3119  } else {
3120  $w2 = '';
3121  }
3122  return array( $w1, $trimmed, $w2 );
3123  }
3124 
3144  function replaceVariables( $text, $frame = false, $argsOnly = false ) {
3145  # Is there any text? Also, Prevent too big inclusions!
3146  if ( strlen( $text ) < 1 || strlen( $text ) > $this->mOptions->getMaxIncludeSize() ) {
3147  return $text;
3148  }
3149  wfProfileIn( __METHOD__ );
3150 
3151  if ( $frame === false ) {
3152  $frame = $this->getPreprocessor()->newFrame();
3153  } elseif ( !( $frame instanceof PPFrame ) ) {
3154  wfDebug( __METHOD__ . " called using plain parameters instead of a PPFrame instance. Creating custom frame.\n" );
3155  $frame = $this->getPreprocessor()->newCustomFrame( $frame );
3156  }
3157 
3158  $dom = $this->preprocessToDom( $text );
3159  $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
3160  $text = $frame->expand( $dom, $flags );
3161 
3162  wfProfileOut( __METHOD__ );
3163  return $text;
3164  }
3165 
3173  static function createAssocArgs( $args ) {
3174  $assocArgs = array();
3175  $index = 1;
3176  foreach ( $args as $arg ) {
3177  $eqpos = strpos( $arg, '=' );
3178  if ( $eqpos === false ) {
3179  $assocArgs[$index++] = $arg;
3180  } else {
3181  $name = trim( substr( $arg, 0, $eqpos ) );
3182  $value = trim( substr( $arg, $eqpos + 1 ) );
3183  if ( $value === false ) {
3184  $value = '';
3185  }
3186  if ( $name !== false ) {
3187  $assocArgs[$name] = $value;
3188  }
3189  }
3190  }
3191 
3192  return $assocArgs;
3193  }
3194 
3219  function limitationWarn( $limitationType, $current = '', $max = '' ) {
3220  # does no harm if $current and $max are present but are unnecessary for the message
3221  $warning = wfMessage( "$limitationType-warning" )->numParams( $current, $max )
3222  ->inLanguage( $this->mOptions->getUserLangObj() )->text();
3223  $this->mOutput->addWarning( $warning );
3224  $this->addTrackingCategory( "$limitationType-category" );
3225  }
3226 
3240  function braceSubstitution( $piece, $frame ) {
3241  wfProfileIn( __METHOD__ );
3242  wfProfileIn( __METHOD__ . '-setup' );
3243 
3244  # Flags
3245  $found = false; # $text has been filled
3246  $nowiki = false; # wiki markup in $text should be escaped
3247  $isHTML = false; # $text is HTML, armour it against wikitext transformation
3248  $forceRawInterwiki = false; # Force interwiki transclusion to be done in raw mode not rendered
3249  $isChildObj = false; # $text is a DOM node needing expansion in a child frame
3250  $isLocalObj = false; # $text is a DOM node needing expansion in the current frame
3251 
3252  # Title object, where $text came from
3253  $title = false;
3254 
3255  # $part1 is the bit before the first |, and must contain only title characters.
3256  # Various prefixes will be stripped from it later.
3257  $titleWithSpaces = $frame->expand( $piece['title'] );
3258  $part1 = trim( $titleWithSpaces );
3259  $titleText = false;
3260 
3261  # Original title text preserved for various purposes
3262  $originalTitle = $part1;
3263 
3264  # $args is a list of argument nodes, starting from index 0, not including $part1
3265  # @todo FIXME: If piece['parts'] is null then the call to getLength() below won't work b/c this $args isn't an object
3266  $args = ( null == $piece['parts'] ) ? array() : $piece['parts'];
3267  wfProfileOut( __METHOD__ . '-setup' );
3268 
3269  $titleProfileIn = null; // profile templates
3270 
3271  # SUBST
3272  wfProfileIn( __METHOD__ . '-modifiers' );
3273  if ( !$found ) {
3274 
3275  $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3276 
3277  # Possibilities for substMatch: "subst", "safesubst" or FALSE
3278  # Decide whether to expand template or keep wikitext as-is.
3279  if ( $this->ot['wiki'] ) {
3280  if ( $substMatch === false ) {
3281  $literal = true; # literal when in PST with no prefix
3282  } else {
3283  $literal = false; # expand when in PST with subst: or safesubst:
3284  }
3285  } else {
3286  if ( $substMatch == 'subst' ) {
3287  $literal = true; # literal when not in PST with plain subst:
3288  } else {
3289  $literal = false; # expand when not in PST with safesubst: or no prefix
3290  }
3291  }
3292  if ( $literal ) {
3293  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3294  $isLocalObj = true;
3295  $found = true;
3296  }
3297  }
3298 
3299  # Variables
3300  if ( !$found && $args->getLength() == 0 ) {
3301  $id = $this->mVariables->matchStartToEnd( $part1 );
3302  if ( $id !== false ) {
3303  $text = $this->getVariableValue( $id, $frame );
3304  if ( MagicWord::getCacheTTL( $id ) > -1 ) {
3305  $this->mOutput->updateCacheExpiry( MagicWord::getCacheTTL( $id ) );
3306  }
3307  $found = true;
3308  }
3309  }
3310 
3311  # MSG, MSGNW and RAW
3312  if ( !$found ) {
3313  # Check for MSGNW:
3314  $mwMsgnw = MagicWord::get( 'msgnw' );
3315  if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3316  $nowiki = true;
3317  } else {
3318  # Remove obsolete MSG:
3319  $mwMsg = MagicWord::get( 'msg' );
3320  $mwMsg->matchStartAndRemove( $part1 );
3321  }
3322 
3323  # Check for RAW:
3324  $mwRaw = MagicWord::get( 'raw' );
3325  if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3326  $forceRawInterwiki = true;
3327  }
3328  }
3329  wfProfileOut( __METHOD__ . '-modifiers' );
3330 
3331  # Parser functions
3332  if ( !$found ) {
3333  wfProfileIn( __METHOD__ . '-pfunc' );
3334 
3335  $colonPos = strpos( $part1, ':' );
3336  if ( $colonPos !== false ) {
3337  $func = substr( $part1, 0, $colonPos );
3338  $funcArgs = array( trim( substr( $part1, $colonPos + 1 ) ) );
3339  for ( $i = 0; $i < $args->getLength(); $i++ ) {
3340  $funcArgs[] = $args->item( $i );
3341  }
3342  try {
3343  $result = $this->callParserFunction( $frame, $func, $funcArgs );
3344  } catch ( Exception $ex ) {
3345  wfProfileOut( __METHOD__ . '-pfunc' );
3346  wfProfileOut( __METHOD__ );
3347  throw $ex;
3348  }
3349 
3350  # The interface for parser functions allows for extracting
3351  # flags into the local scope. Extract any forwarded flags
3352  # here.
3353  extract( $result );
3354  }
3355  wfProfileOut( __METHOD__ . '-pfunc' );
3356  }
3357 
3358  # Finish mangling title and then check for loops.
3359  # Set $title to a Title object and $titleText to the PDBK
3360  if ( !$found ) {
3361  $ns = NS_TEMPLATE;
3362  # Split the title into page and subpage
3363  $subpage = '';
3364  $relative = $this->maybeDoSubpageLink( $part1, $subpage );
3365  if ( $part1 !== $relative ) {
3366  $part1 = $relative;
3367  $ns = $this->mTitle->getNamespace();
3368  }
3369  $title = Title::newFromText( $part1, $ns );
3370  if ( $title ) {
3371  $titleText = $title->getPrefixedText();
3372  # Check for language variants if the template is not found
3373  if ( $this->getConverterLanguage()->hasVariants() && $title->getArticleID() == 0 ) {
3374  $this->getConverterLanguage()->findVariantLink( $part1, $title, true );
3375  }
3376  # Do recursion depth check
3377  $limit = $this->mOptions->getMaxTemplateDepth();
3378  if ( $frame->depth >= $limit ) {
3379  $found = true;
3380  $text = '<span class="error">'
3381  . wfMessage( 'parser-template-recursion-depth-warning' )
3382  ->numParams( $limit )->inContentLanguage()->text()
3383  . '</span>';
3384  }
3385  }
3386  }
3387 
3388  # Load from database
3389  if ( !$found && $title ) {
3390  if ( !Profiler::instance()->isPersistent() ) {
3391  # Too many unique items can kill profiling DBs/collectors
3392  $titleProfileIn = __METHOD__ . "-title-" . $title->getPrefixedDBkey();
3393  wfProfileIn( $titleProfileIn ); // template in
3394  }
3395  wfProfileIn( __METHOD__ . '-loadtpl' );
3396  if ( !$title->isExternal() ) {
3397  if ( $title->isSpecialPage()
3398  && $this->mOptions->getAllowSpecialInclusion()
3399  && $this->ot['html']
3400  ) {
3401  // Pass the template arguments as URL parameters.
3402  // "uselang" will have no effect since the Language object
3403  // is forced to the one defined in ParserOptions.
3404  $pageArgs = array();
3405  for ( $i = 0; $i < $args->getLength(); $i++ ) {
3406  $bits = $args->item( $i )->splitArg();
3407  if ( strval( $bits['index'] ) === '' ) {
3408  $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
3409  $value = trim( $frame->expand( $bits['value'] ) );
3410  $pageArgs[$name] = $value;
3411  }
3412  }
3413 
3414  // Create a new context to execute the special page
3415  $context = new RequestContext;
3416  $context->setTitle( $title );
3417  $context->setRequest( new FauxRequest( $pageArgs ) );
3418  $context->setUser( $this->getUser() );
3419  $context->setLanguage( $this->mOptions->getUserLangObj() );
3421  if ( $ret ) {
3422  $text = $context->getOutput()->getHTML();
3423  $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3424  $found = true;
3425  $isHTML = true;
3426  $this->disableCache();
3427  }
3428  } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
3429  $found = false; # access denied
3430  wfDebug( __METHOD__ . ": template inclusion denied for " .
3431  $title->getPrefixedDBkey() . "\n" );
3432  } else {
3433  list( $text, $title ) = $this->getTemplateDom( $title );
3434  if ( $text !== false ) {
3435  $found = true;
3436  $isChildObj = true;
3437  }
3438  }
3439 
3440  # If the title is valid but undisplayable, make a link to it
3441  if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3442  $text = "[[:$titleText]]";
3443  $found = true;
3444  }
3445  } elseif ( $title->isTrans() ) {
3446  # Interwiki transclusion
3447  if ( $this->ot['html'] && !$forceRawInterwiki ) {
3448  $text = $this->interwikiTransclude( $title, 'render' );
3449  $isHTML = true;
3450  } else {
3451  $text = $this->interwikiTransclude( $title, 'raw' );
3452  # Preprocess it like a template
3453  $text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3454  $isChildObj = true;
3455  }
3456  $found = true;
3457  }
3458 
3459  # Do infinite loop check
3460  # This has to be done after redirect resolution to avoid infinite loops via redirects
3461  if ( !$frame->loopCheck( $title ) ) {
3462  $found = true;
3463  $text = '<span class="error">'
3464  . wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3465  . '</span>';
3466  wfDebug( __METHOD__ . ": template loop broken at '$titleText'\n" );
3467  }
3468  wfProfileOut( __METHOD__ . '-loadtpl' );
3469  }
3470 
3471  # If we haven't found text to substitute by now, we're done
3472  # Recover the source wikitext and return it
3473  if ( !$found ) {
3474  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3475  if ( $titleProfileIn ) {
3476  wfProfileOut( $titleProfileIn ); // template out
3477  }
3478  wfProfileOut( __METHOD__ );
3479  return array( 'object' => $text );
3480  }
3481 
3482  # Expand DOM-style return values in a child frame
3483  if ( $isChildObj ) {
3484  # Clean up argument array
3485  $newFrame = $frame->newChild( $args, $title );
3486 
3487  if ( $nowiki ) {
3488  $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3489  } elseif ( $titleText !== false && $newFrame->isEmpty() ) {
3490  # Expansion is eligible for the empty-frame cache
3491  if ( isset( $this->mTplExpandCache[$titleText] ) ) {
3492  $text = $this->mTplExpandCache[$titleText];
3493  } else {
3494  $text = $newFrame->expand( $text );
3495  $this->mTplExpandCache[$titleText] = $text;
3496  }
3497  } else {
3498  # Uncached expansion
3499  $text = $newFrame->expand( $text );
3500  }
3501  }
3502  if ( $isLocalObj && $nowiki ) {
3503  $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
3504  $isLocalObj = false;
3505  }
3506 
3507  if ( $titleProfileIn ) {
3508  wfProfileOut( $titleProfileIn ); // template out
3509  }
3510 
3511  # Replace raw HTML by a placeholder
3512  if ( $isHTML ) {
3513  $text = $this->insertStripItem( $text );
3514  } elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3515  # Escape nowiki-style return values
3516  $text = wfEscapeWikiText( $text );
3517  } elseif ( is_string( $text )
3518  && !$piece['lineStart']
3519  && preg_match( '/^(?:{\\||:|;|#|\*)/', $text )
3520  ) {
3521  # Bug 529: if the template begins with a table or block-level
3522  # element, it should be treated as beginning a new line.
3523  # This behavior is somewhat controversial.
3524  $text = "\n" . $text;
3525  }
3526 
3527  if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
3528  # Error, oversize inclusion
3529  if ( $titleText !== false ) {
3530  # Make a working, properly escaped link if possible (bug 23588)
3531  $text = "[[:$titleText]]";
3532  } else {
3533  # This will probably not be a working link, but at least it may
3534  # provide some hint of where the problem is
3535  preg_replace( '/^:/', '', $originalTitle );
3536  $text = "[[:$originalTitle]]";
3537  }
3538  $text .= $this->insertStripItem( '<!-- WARNING: template omitted, post-expand include size too large -->' );
3539  $this->limitationWarn( 'post-expand-template-inclusion' );
3540  }
3541 
3542  if ( $isLocalObj ) {
3543  $ret = array( 'object' => $text );
3544  } else {
3545  $ret = array( 'text' => $text );
3546  }
3547 
3548  wfProfileOut( __METHOD__ );
3549  return $ret;
3550  }
3551 
3570  public function callParserFunction( $frame, $function, array $args = array() ) {
3572 
3573  wfProfileIn( __METHOD__ );
3574 
3575  # Case sensitive functions
3576  if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3577  $function = $this->mFunctionSynonyms[1][$function];
3578  } else {
3579  # Case insensitive functions
3580  $function = $wgContLang->lc( $function );
3581  if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3582  $function = $this->mFunctionSynonyms[0][$function];
3583  } else {
3584  wfProfileOut( __METHOD__ );
3585  return array( 'found' => false );
3586  }
3587  }
3588 
3589  wfProfileIn( __METHOD__ . '-pfunc-' . $function );
3590  list( $callback, $flags ) = $this->mFunctionHooks[$function];
3591 
3592  # Workaround for PHP bug 35229 and similar
3593  if ( !is_callable( $callback ) ) {
3594  wfProfileOut( __METHOD__ . '-pfunc-' . $function );
3595  wfProfileOut( __METHOD__ );
3596  throw new MWException( "Tag hook for $function is not callable\n" );
3597  }
3598 
3599  $allArgs = array( &$this );
3600  if ( $flags & SFH_OBJECT_ARGS ) {
3601  # Convert arguments to PPNodes and collect for appending to $allArgs
3602  $funcArgs = array();
3603  foreach ( $args as $k => $v ) {
3604  if ( $v instanceof PPNode || $k === 0 ) {
3605  $funcArgs[] = $v;
3606  } else {
3607  $funcArgs[] = $this->mPreprocessor->newPartNodeArray( array( $k => $v ) )->item( 0 );
3608  }
3609  }
3610 
3611  # Add a frame parameter, and pass the arguments as an array
3612  $allArgs[] = $frame;
3613  $allArgs[] = $funcArgs;
3614  } else {
3615  # Convert arguments to plain text and append to $allArgs
3616  foreach ( $args as $k => $v ) {
3617  if ( $v instanceof PPNode ) {
3618  $allArgs[] = trim( $frame->expand( $v ) );
3619  } elseif ( is_int( $k ) && $k >= 0 ) {
3620  $allArgs[] = trim( $v );
3621  } else {
3622  $allArgs[] = trim( "$k=$v" );
3623  }
3624  }
3625  }
3626 
3627  $result = call_user_func_array( $callback, $allArgs );
3628 
3629  # The interface for function hooks allows them to return a wikitext
3630  # string or an array containing the string and any flags. This mungs
3631  # things around to match what this method should return.
3632  if ( !is_array( $result ) ) {
3633  $result = array(
3634  'found' => true,
3635  'text' => $result,
3636  );
3637  } else {
3638  if ( isset( $result[0] ) && !isset( $result['text'] ) ) {
3639  $result['text'] = $result[0];
3640  }
3641  unset( $result[0] );
3642  $result += array(
3643  'found' => true,
3644  );
3645  }
3646 
3647  $noparse = true;
3648  $preprocessFlags = 0;
3649  if ( isset( $result['noparse'] ) ) {
3650  $noparse = $result['noparse'];
3651  }
3652  if ( isset( $result['preprocessFlags'] ) ) {
3653  $preprocessFlags = $result['preprocessFlags'];
3654  }
3655 
3656  if ( !$noparse ) {
3657  $result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
3658  $result['isChildObj'] = true;
3659  }
3660  wfProfileOut( __METHOD__ . '-pfunc-' . $function );
3661  wfProfileOut( __METHOD__ );
3662 
3663  return $result;
3664  }
3665 
3674  function getTemplateDom( $title ) {
3675  $cacheTitle = $title;
3676  $titleText = $title->getPrefixedDBkey();
3677 
3678  if ( isset( $this->mTplRedirCache[$titleText] ) ) {
3679  list( $ns, $dbk ) = $this->mTplRedirCache[$titleText];
3680  $title = Title::makeTitle( $ns, $dbk );
3681  $titleText = $title->getPrefixedDBkey();
3682  }
3683  if ( isset( $this->mTplDomCache[$titleText] ) ) {
3684  return array( $this->mTplDomCache[$titleText], $title );
3685  }
3686 
3687  # Cache miss, go to the database
3688  list( $text, $title ) = $this->fetchTemplateAndTitle( $title );
3689 
3690  if ( $text === false ) {
3691  $this->mTplDomCache[$titleText] = false;
3692  return array( false, $title );
3693  }
3694 
3695  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3696  $this->mTplDomCache[$titleText] = $dom;
3697 
3698  if ( !$title->equals( $cacheTitle ) ) {
3699  $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
3700  array( $title->getNamespace(), $cdb = $title->getDBkey() );
3701  }
3702 
3703  return array( $dom, $title );
3704  }
3705 
3711  function fetchTemplateAndTitle( $title ) {
3712  $templateCb = $this->mOptions->getTemplateCallback(); # Defaults to Parser::statelessFetchTemplate()
3713  $stuff = call_user_func( $templateCb, $title, $this );
3714  $text = $stuff['text'];
3715  $finalTitle = isset( $stuff['finalTitle'] ) ? $stuff['finalTitle'] : $title;
3716  if ( isset( $stuff['deps'] ) ) {
3717  foreach ( $stuff['deps'] as $dep ) {
3718  $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
3719  if ( $dep['title']->equals( $this->getTitle() ) ) {
3720  // If we transclude ourselves, the final result
3721  // will change based on the new version of the page
3722  $this->mOutput->setFlag( 'vary-revision' );
3723  }
3724  }
3725  }
3726  return array( $text, $finalTitle );
3727  }
3728 
3734  function fetchTemplate( $title ) {
3735  $rv = $this->fetchTemplateAndTitle( $title );
3736  return $rv[0];
3737  }
3738 
3748  static function statelessFetchTemplate( $title, $parser = false ) {
3749  $text = $skip = false;
3750  $finalTitle = $title;
3751  $deps = array();
3752 
3753  # Loop to fetch the article, with up to 1 redirect
3754  for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
3755  # Give extensions a chance to select the revision instead
3756  $id = false; # Assume current
3757  wfRunHooks( 'BeforeParserFetchTemplateAndtitle',
3758  array( $parser, $title, &$skip, &$id ) );
3759 
3760  if ( $skip ) {
3761  $text = false;
3762  $deps[] = array(
3763  'title' => $title,
3764  'page_id' => $title->getArticleID(),
3765  'rev_id' => null
3766  );
3767  break;
3768  }
3769  # Get the revision
3770  $rev = $id
3771  ? Revision::newFromId( $id )
3772  : Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
3773  $rev_id = $rev ? $rev->getId() : 0;
3774  # If there is no current revision, there is no page
3775  if ( $id === false && !$rev ) {
3776  $linkCache = LinkCache::singleton();
3777  $linkCache->addBadLinkObj( $title );
3778  }
3779 
3780  $deps[] = array(
3781  'title' => $title,
3782  'page_id' => $title->getArticleID(),
3783  'rev_id' => $rev_id );
3784  if ( $rev && !$title->equals( $rev->getTitle() ) ) {
3785  # We fetched a rev from a different title; register it too...
3786  $deps[] = array(
3787  'title' => $rev->getTitle(),
3788  'page_id' => $rev->getPage(),
3789  'rev_id' => $rev_id );
3790  }
3791 
3792  if ( $rev ) {
3793  $content = $rev->getContent();
3794  $text = $content ? $content->getWikitextForTransclusion() : null;
3795 
3796  if ( $text === false || $text === null ) {
3797  $text = false;
3798  break;
3799  }
3800  } elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
3802  $message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
3803  if ( !$message->exists() ) {
3804  $text = false;
3805  break;
3806  }
3807  $content = $message->content();
3808  $text = $message->plain();
3809  } else {
3810  break;
3811  }
3812  if ( !$content ) {
3813  break;
3814  }
3815  # Redirect?
3816  $finalTitle = $title;
3817  $title = $content->getRedirectTarget();
3818  }
3819  return array(
3820  'text' => $text,
3821  'finalTitle' => $finalTitle,
3822  'deps' => $deps );
3823  }
3824 
3832  function fetchFile( $title, $options = array() ) {
3833  $res = $this->fetchFileAndTitle( $title, $options );
3834  return $res[0];
3835  }
3836 
3844  function fetchFileAndTitle( $title, $options = array() ) {
3845  $file = $this->fetchFileNoRegister( $title, $options );
3846 
3847  $time = $file ? $file->getTimestamp() : false;
3848  $sha1 = $file ? $file->getSha1() : false;
3849  # Register the file as a dependency...
3850  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3851  if ( $file && !$title->equals( $file->getTitle() ) ) {
3852  # Update fetched file title
3853  $title = $file->getTitle();
3854  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3855  }
3856  return array( $file, $title );
3857  }
3858 
3869  protected function fetchFileNoRegister( $title, $options = array() ) {
3870  if ( isset( $options['broken'] ) ) {
3871  $file = false; // broken thumbnail forced by hook
3872  } elseif ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
3873  $file = RepoGroup::singleton()->findFileFromKey( $options['sha1'], $options );
3874  } else { // get by (name,timestamp)
3876  }
3877  return $file;
3878  }
3879 
3888  function interwikiTransclude( $title, $action ) {
3889  global $wgEnableScaryTranscluding;
3890 
3891  if ( !$wgEnableScaryTranscluding ) {
3892  return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
3893  }
3894 
3895  $url = $title->getFullURL( array( 'action' => $action ) );
3896 
3897  if ( strlen( $url ) > 255 ) {
3898  return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
3899  }
3900  return $this->fetchScaryTemplateMaybeFromCache( $url );
3901  }
3902 
3907  function fetchScaryTemplateMaybeFromCache( $url ) {
3908  global $wgTranscludeCacheExpiry;
3909  $dbr = wfGetDB( DB_SLAVE );
3910  $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
3911  $obj = $dbr->selectRow( 'transcache', array( 'tc_time', 'tc_contents' ),
3912  array( 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ) );
3913  if ( $obj ) {
3914  return $obj->tc_contents;
3915  }
3916 
3917  $req = MWHttpRequest::factory( $url );
3918  $status = $req->execute(); // Status object
3919  if ( $status->isOK() ) {
3920  $text = $req->getContent();
3921  } elseif ( $req->getStatus() != 200 ) { // Though we failed to fetch the content, this status is useless.
3922  return wfMessage( 'scarytranscludefailed-httpstatus', $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text();
3923  } else {
3924  return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
3925  }
3926 
3927  $dbw = wfGetDB( DB_MASTER );
3928  $dbw->replace( 'transcache', array( 'tc_url' ), array(
3929  'tc_url' => $url,
3930  'tc_time' => $dbw->timestamp( time() ),
3931  'tc_contents' => $text
3932  ) );
3933  return $text;
3934  }
3935 
3945  function argSubstitution( $piece, $frame ) {
3946  wfProfileIn( __METHOD__ );
3947 
3948  $error = false;
3949  $parts = $piece['parts'];
3950  $nameWithSpaces = $frame->expand( $piece['title'] );
3951  $argName = trim( $nameWithSpaces );
3952  $object = false;
3953  $text = $frame->getArgument( $argName );
3954  if ( $text === false && $parts->getLength() > 0
3955  && ( $this->ot['html']
3956  || $this->ot['pre']
3957  || ( $this->ot['wiki'] && $frame->isTemplate() )
3958  )
3959  ) {
3960  # No match in frame, use the supplied default
3961  $object = $parts->item( 0 )->getChildren();
3962  }
3963  if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
3964  $error = '<!-- WARNING: argument omitted, expansion size too large -->';
3965  $this->limitationWarn( 'post-expand-template-argument' );
3966  }
3967 
3968  if ( $text === false && $object === false ) {
3969  # No match anywhere
3970  $object = $frame->virtualBracketedImplode( '{{{', '|', '}}}', $nameWithSpaces, $parts );
3971  }
3972  if ( $error !== false ) {
3973  $text .= $error;
3974  }
3975  if ( $object !== false ) {
3976  $ret = array( 'object' => $object );
3977  } else {
3978  $ret = array( 'text' => $text );
3979  }
3980 
3981  wfProfileOut( __METHOD__ );
3982  return $ret;
3983  }
3984 
4000  function extensionSubstitution( $params, $frame ) {
4001  $name = $frame->expand( $params['name'] );
4002  $attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
4003  $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
4004  $marker = "{$this->mUniqPrefix}-$name-" . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
4005 
4006  $isFunctionTag = isset( $this->mFunctionTagHooks[strtolower( $name )] ) &&
4007  ( $this->ot['html'] || $this->ot['pre'] );
4008  if ( $isFunctionTag ) {
4009  $markerType = 'none';
4010  } else {
4011  $markerType = 'general';
4012  }
4013  if ( $this->ot['html'] || $isFunctionTag ) {
4014  $name = strtolower( $name );
4015  $attributes = Sanitizer::decodeTagAttributes( $attrText );
4016  if ( isset( $params['attributes'] ) ) {
4017  $attributes = $attributes + $params['attributes'];
4018  }
4019 
4020  if ( isset( $this->mTagHooks[$name] ) ) {
4021  # Workaround for PHP bug 35229 and similar
4022  if ( !is_callable( $this->mTagHooks[$name] ) ) {
4023  throw new MWException( "Tag hook for $name is not callable\n" );
4024  }
4025  $output = call_user_func_array( $this->mTagHooks[$name],
4026  array( $content, $attributes, $this, $frame ) );
4027  } elseif ( isset( $this->mFunctionTagHooks[$name] ) ) {
4028  list( $callback, ) = $this->mFunctionTagHooks[$name];
4029  if ( !is_callable( $callback ) ) {
4030  throw new MWException( "Tag hook for $name is not callable\n" );
4031  }
4032 
4033  $output = call_user_func_array( $callback, array( &$this, $frame, $content, $attributes ) );
4034  } else {
4035  $output = '<span class="error">Invalid tag extension name: ' .
4036  htmlspecialchars( $name ) . '</span>';
4037  }
4038 
4039  if ( is_array( $output ) ) {
4040  # Extract flags to local scope (to override $markerType)
4041  $flags = $output;
4042  $output = $flags[0];
4043  unset( $flags[0] );
4044  extract( $flags );
4045  }
4046  } else {
4047  if ( is_null( $attrText ) ) {
4048  $attrText = '';
4049  }
4050  if ( isset( $params['attributes'] ) ) {
4051  foreach ( $params['attributes'] as $attrName => $attrValue ) {
4052  $attrText .= ' ' . htmlspecialchars( $attrName ) . '="' .
4053  htmlspecialchars( $attrValue ) . '"';
4054  }
4055  }
4056  if ( $content === null ) {
4057  $output = "<$name$attrText/>";
4058  } else {
4059  $close = is_null( $params['close'] ) ? '' : $frame->expand( $params['close'] );
4060  $output = "<$name$attrText>$content$close";
4061  }
4062  }
4063 
4064  if ( $markerType === 'none' ) {
4065  return $output;
4066  } elseif ( $markerType === 'nowiki' ) {
4067  $this->mStripState->addNoWiki( $marker, $output );
4068  } elseif ( $markerType === 'general' ) {
4069  $this->mStripState->addGeneral( $marker, $output );
4070  } else {
4071  throw new MWException( __METHOD__ . ': invalid marker type' );
4072  }
4073  return $marker;
4074  }
4075 
4083  function incrementIncludeSize( $type, $size ) {
4084  if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
4085  return false;
4086  } else {
4087  $this->mIncludeSizes[$type] += $size;
4088  return true;
4089  }
4090  }
4091 
4097  function incrementExpensiveFunctionCount() {
4098  $this->mExpensiveFunctionCount++;
4099  return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
4100  }
4101 
4110  function doDoubleUnderscore( $text ) {
4111  wfProfileIn( __METHOD__ );
4112 
4113  # The position of __TOC__ needs to be recorded
4114  $mw = MagicWord::get( 'toc' );
4115  if ( $mw->match( $text ) ) {
4116  $this->mShowToc = true;
4117  $this->mForceTocPosition = true;
4118 
4119  # Set a placeholder. At the end we'll fill it in with the TOC.
4120  $text = $mw->replace( '<!--MWTOC-->', $text, 1 );
4121 
4122  # Only keep the first one.
4123  $text = $mw->replace( '', $text );
4124  }
4125 
4126  # Now match and remove the rest of them
4128  $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
4129 
4130  if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) {
4131  $this->mOutput->mNoGallery = true;
4132  }
4133  if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) {
4134  $this->mShowToc = false;
4135  }
4136  if ( isset( $this->mDoubleUnderscores['hiddencat'] ) && $this->mTitle->getNamespace() == NS_CATEGORY ) {
4137  $this->addTrackingCategory( 'hidden-category-category' );
4138  }
4139  # (bug 8068) Allow control over whether robots index a page.
4140  #
4141  # @todo FIXME: Bug 14899: __INDEX__ always overrides __NOINDEX__ here! This
4142  # is not desirable, the last one on the page should win.
4143  if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->mTitle->canUseNoindex() ) {
4144  $this->mOutput->setIndexPolicy( 'noindex' );
4145  $this->addTrackingCategory( 'noindex-category' );
4146  }
4147  if ( isset( $this->mDoubleUnderscores['index'] ) && $this->mTitle->canUseNoindex() ) {
4148  $this->mOutput->setIndexPolicy( 'index' );
4149  $this->addTrackingCategory( 'index-category' );
4150  }
4151 
4152  # Cache all double underscores in the database
4153  foreach ( $this->mDoubleUnderscores as $key => $val ) {
4154  $this->mOutput->setProperty( $key, '' );
4155  }
4156 
4157  wfProfileOut( __METHOD__ );
4158  return $text;
4159  }
4160 
4172  public function addTrackingCategory( $msg ) {
4173  if ( $this->mTitle->getNamespace() === NS_SPECIAL ) {
4174  wfDebug( __METHOD__ . ": Not adding tracking category $msg to special page!\n" );
4175  return false;
4176  }
4177  // Important to parse with correct title (bug 31469)
4178  $cat = wfMessage( $msg )
4179  ->title( $this->getTitle() )
4180  ->inContentLanguage()
4181  ->text();
4182 
4183  # Allow tracking categories to be disabled by setting them to "-"
4184  if ( $cat === '-' ) {
4185  return false;
4186  }
4187 
4188  $containerCategory = Title::makeTitleSafe( NS_CATEGORY, $cat );
4189  if ( $containerCategory ) {
4190  $this->mOutput->addCategory( $containerCategory->getDBkey(), $this->getDefaultSort() );
4191  return true;
4192  } else {
4193  wfDebug( __METHOD__ . ": [[MediaWiki:$msg]] is not a valid title!\n" );
4194  return false;
4195  }
4196  }
4197 
4214  function formatHeadings( $text, $origText, $isMain = true ) {
4215  global $wgMaxTocLevel, $wgExperimentalHtmlIds;
4216 
4217  # Inhibit editsection links if requested in the page
4218  if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
4219  $maybeShowEditLink = $showEditLink = false;
4220  } else {
4221  $maybeShowEditLink = true; /* Actual presence will depend on ParserOptions option */
4222  $showEditLink = $this->mOptions->getEditSection();
4223  }
4224  if ( $showEditLink ) {
4225  $this->mOutput->setEditSectionTokens( true );
4226  }
4227 
4228  # Get all headlines for numbering them and adding funky stuff like [edit]
4229  # links - this is for later, but we need the number of headlines right now
4230  $matches = array();
4231  $numMatches = preg_match_all( '/<H(?P<level>[1-6])(?P<attrib>.*?' . '>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i', $text, $matches );
4232 
4233  # if there are fewer than 4 headlines in the article, do not show TOC
4234  # unless it's been explicitly enabled.
4235  $enoughToc = $this->mShowToc &&
4236  ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4237 
4238  # Allow user to stipulate that a page should have a "new section"
4239  # link added via __NEWSECTIONLINK__
4240  if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) {
4241  $this->mOutput->setNewSection( true );
4242  }
4243 
4244  # Allow user to remove the "new section"
4245  # link via __NONEWSECTIONLINK__
4246  if ( isset( $this->mDoubleUnderscores['nonewsectionlink'] ) ) {
4247  $this->mOutput->hideNewSection( true );
4248  }
4249 
4250  # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4251  # override above conditions and always show TOC above first header
4252  if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) {
4253  $this->mShowToc = true;
4254  $enoughToc = true;
4255  }
4256 
4257  # headline counter
4258  $headlineCount = 0;
4259  $numVisible = 0;
4260 
4261  # Ugh .. the TOC should have neat indentation levels which can be
4262  # passed to the skin functions. These are determined here
4263  $toc = '';
4264  $full = '';
4265  $head = array();
4266  $sublevelCount = array();
4267  $levelCount = array();
4268  $level = 0;
4269  $prevlevel = 0;
4270  $toclevel = 0;
4271  $prevtoclevel = 0;
4272  $markerRegex = "{$this->mUniqPrefix}-h-(\d+)-" . self::MARKER_SUFFIX;
4273  $baseTitleText = $this->mTitle->getPrefixedDBkey();
4274  $oldType = $this->mOutputType;
4275  $this->setOutputType( self::OT_WIKI );
4276  $frame = $this->getPreprocessor()->newFrame();
4277  $root = $this->preprocessToDom( $origText );
4278  $node = $root->getFirstChild();
4279  $byteOffset = 0;
4280  $tocraw = array();
4281  $refers = array();
4282 
4283  foreach ( $matches[3] as $headline ) {
4284  $isTemplate = false;
4285  $titleText = false;
4286  $sectionIndex = false;
4287  $numbering = '';
4288  $markerMatches = array();
4289  if ( preg_match( "/^$markerRegex/", $headline, $markerMatches ) ) {
4290  $serial = $markerMatches[1];
4291  list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
4292  $isTemplate = ( $titleText != $baseTitleText );
4293  $headline = preg_replace( "/^$markerRegex\\s*/", "", $headline );
4294  }
4295 
4296  if ( $toclevel ) {
4297  $prevlevel = $level;
4298  }
4299  $level = $matches[1][$headlineCount];
4300 
4301  if ( $level > $prevlevel ) {
4302  # Increase TOC level
4303  $toclevel++;
4304  $sublevelCount[$toclevel] = 0;
4305  if ( $toclevel < $wgMaxTocLevel ) {
4306  $prevtoclevel = $toclevel;
4307  $toc .= Linker::tocIndent();
4308  $numVisible++;
4309  }
4310  } elseif ( $level < $prevlevel && $toclevel > 1 ) {
4311  # Decrease TOC level, find level to jump to
4312 
4313  for ( $i = $toclevel; $i > 0; $i-- ) {
4314  if ( $levelCount[$i] == $level ) {
4315  # Found last matching level
4316  $toclevel = $i;
4317  break;
4318  } elseif ( $levelCount[$i] < $level ) {
4319  # Found first matching level below current level
4320  $toclevel = $i + 1;
4321  break;
4322  }
4323  }
4324  if ( $i == 0 ) {
4325  $toclevel = 1;
4326  }
4327  if ( $toclevel < $wgMaxTocLevel ) {
4328  if ( $prevtoclevel < $wgMaxTocLevel ) {
4329  # Unindent only if the previous toc level was shown :p
4330  $toc .= Linker::tocUnindent( $prevtoclevel - $toclevel );
4331  $prevtoclevel = $toclevel;
4332  } else {
4333  $toc .= Linker::tocLineEnd();
4334  }
4335  }
4336  } else {
4337  # No change in level, end TOC line
4338  if ( $toclevel < $wgMaxTocLevel ) {
4339  $toc .= Linker::tocLineEnd();
4340  }
4341  }
4342 
4343  $levelCount[$toclevel] = $level;
4344 
4345  # count number of headlines for each level
4346  $sublevelCount[$toclevel]++;
4347  $dot = 0;
4348  for ( $i = 1; $i <= $toclevel; $i++ ) {
4349  if ( !empty( $sublevelCount[$i] ) ) {
4350  if ( $dot ) {
4351  $numbering .= '.';
4352  }
4353  $numbering .= $this->getTargetLanguage()->formatNum( $sublevelCount[$i] );
4354  $dot = 1;
4355  }
4356  }
4357 
4358  # The safe header is a version of the header text safe to use for links
4359 
4360  # Remove link placeholders by the link text.
4361  # <!--LINK number-->
4362  # turns into
4363  # link text with suffix
4364  # Do this before unstrip since link text can contain strip markers
4365  $safeHeadline = $this->replaceLinkHoldersText( $headline );
4366 
4367  # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4368  $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4369 
4370  # Strip out HTML (first regex removes any tag not allowed)
4371  # Allowed tags are:
4372  # * <sup> and <sub> (bug 8393)
4373  # * <i> (bug 26375)
4374  # * <b> (r105284)
4375  # * <span dir="rtl"> and <span dir="ltr"> (bug 35167)
4376  #
4377  # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4378  # to allow setting directionality in toc items.
4379  $tocline = preg_replace(
4380  array( '#<(?!/?(span|sup|sub|i|b)(?: [^>]*)?>).*?' . '>#', '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|i|b))(?: .*?)?' . '>#' ),
4381  array( '', '<$1>' ),
4382  $safeHeadline
4383  );
4384  $tocline = trim( $tocline );
4385 
4386  # For the anchor, strip out HTML-y stuff period
4387  $safeHeadline = preg_replace( '/<.*?' . '>/', '', $safeHeadline );
4388  $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4389 
4390  # Save headline for section edit hint before it's escaped
4391  $headlineHint = $safeHeadline;
4392 
4393  if ( $wgExperimentalHtmlIds ) {
4394  # For reverse compatibility, provide an id that's
4395  # HTML4-compatible, like we used to.
4396  #
4397  # It may be worth noting, academically, that it's possible for
4398  # the legacy anchor to conflict with a non-legacy headline
4399  # anchor on the page. In this case likely the "correct" thing
4400  # would be to either drop the legacy anchors or make sure
4401  # they're numbered first. However, this would require people
4402  # to type in section names like "abc_.D7.93.D7.90.D7.A4"
4403  # manually, so let's not bother worrying about it.
4404  $legacyHeadline = Sanitizer::escapeId( $safeHeadline,
4405  array( 'noninitial', 'legacy' ) );
4406  $safeHeadline = Sanitizer::escapeId( $safeHeadline );
4407 
4408  if ( $legacyHeadline == $safeHeadline ) {
4409  # No reason to have both (in fact, we can't)
4410  $legacyHeadline = false;
4411  }
4412  } else {
4413  $legacyHeadline = false;
4414  $safeHeadline = Sanitizer::escapeId( $safeHeadline,
4415  'noninitial' );
4416  }
4417 
4418  # HTML names must be case-insensitively unique (bug 10721).
4419  # This does not apply to Unicode characters per
4420  # http://dev.w3.org/html5/spec/infrastructure.html#case-sensitivity-and-string-comparison
4421  # @todo FIXME: We may be changing them depending on the current locale.
4422  $arrayKey = strtolower( $safeHeadline );
4423  if ( $legacyHeadline === false ) {
4424  $legacyArrayKey = false;
4425  } else {
4426  $legacyArrayKey = strtolower( $legacyHeadline );
4427  }
4428 
4429  # count how many in assoc. array so we can track dupes in anchors
4430  if ( isset( $refers[$arrayKey] ) ) {
4431  $refers[$arrayKey]++;
4432  } else {
4433  $refers[$arrayKey] = 1;
4434  }
4435  if ( isset( $refers[$legacyArrayKey] ) ) {
4436  $refers[$legacyArrayKey]++;
4437  } else {
4438  $refers[$legacyArrayKey] = 1;
4439  }
4440 
4441  # Don't number the heading if it is the only one (looks silly)
4442  if ( count( $matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) {
4443  # the two are different if the line contains a link
4444  $headline = Html::element( 'span', array( 'class' => 'mw-headline-number' ), $numbering ) . ' ' . $headline;
4445  }
4446 
4447  # Create the anchor for linking from the TOC to the section
4448  $anchor = $safeHeadline;
4449  $legacyAnchor = $legacyHeadline;
4450  if ( $refers[$arrayKey] > 1 ) {
4451  $anchor .= '_' . $refers[$arrayKey];
4452  }
4453  if ( $legacyHeadline !== false && $refers[$legacyArrayKey] > 1 ) {
4454  $legacyAnchor .= '_' . $refers[$legacyArrayKey];
4455  }
4456  if ( $enoughToc && ( !isset( $wgMaxTocLevel ) || $toclevel < $wgMaxTocLevel ) ) {
4457  $toc .= Linker::tocLine( $anchor, $tocline,
4458  $numbering, $toclevel, ( $isTemplate ? false : $sectionIndex ) );
4459  }
4460 
4461  # Add the section to the section tree
4462  # Find the DOM node for this header
4463  $noOffset = ( $isTemplate || $sectionIndex === false );
4464  while ( $node && !$noOffset ) {
4465  if ( $node->getName() === 'h' ) {
4466  $bits = $node->splitHeading();
4467  if ( $bits['i'] == $sectionIndex ) {
4468  break;
4469  }
4470  }
4471  $byteOffset += mb_strlen( $this->mStripState->unstripBoth(
4472  $frame->expand( $node, PPFrame::RECOVER_ORIG ) ) );
4473  $node = $node->getNextSibling();
4474  }
4475  $tocraw[] = array(
4476  'toclevel' => $toclevel,
4477  'level' => $level,
4478  'line' => $tocline,
4479  'number' => $numbering,
4480  'index' => ( $isTemplate ? 'T-' : '' ) . $sectionIndex,
4481  'fromtitle' => $titleText,
4482  'byteoffset' => ( $noOffset ? null : $byteOffset ),
4483  'anchor' => $anchor,
4484  );
4485 
4486  # give headline the correct <h#> tag
4487  if ( $maybeShowEditLink && $sectionIndex !== false ) {
4488  // Output edit section links as markers with styles that can be customized by skins
4489  if ( $isTemplate ) {
4490  # Put a T flag in the section identifier, to indicate to extractSections()
4491  # that sections inside <includeonly> should be counted.
4492  $editlinkArgs = array( $titleText, "T-$sectionIndex"/*, null */ );
4493  } else {
4494  $editlinkArgs = array( $this->mTitle->getPrefixedText(), $sectionIndex, $headlineHint );
4495  }
4496  // We use a bit of pesudo-xml for editsection markers. The language converter is run later on
4497  // Using a UNIQ style marker leads to the converter screwing up the tokens when it converts stuff
4498  // And trying to insert strip tags fails too. At this point all real inputted tags have already been escaped
4499  // so we don't have to worry about a user trying to input one of these markers directly.
4500  // We use a page and section attribute to stop the language converter from converting these important bits
4501  // of data, but put the headline hint inside a content block because the language converter is supposed to
4502  // be able to convert that piece of data.
4503  $editlink = '<mw:editsection page="' . htmlspecialchars( $editlinkArgs[0] );
4504  $editlink .= '" section="' . htmlspecialchars( $editlinkArgs[1] ) . '"';
4505  if ( isset( $editlinkArgs[2] ) ) {
4506  $editlink .= '>' . $editlinkArgs[2] . '</mw:editsection>';
4507  } else {
4508  $editlink .= '/>';
4509  }
4510  } else {
4511  $editlink = '';
4512  }
4513  $head[$headlineCount] = Linker::makeHeadline( $level,
4514  $matches['attrib'][$headlineCount], $anchor, $headline,
4515  $editlink, $legacyAnchor );
4516 
4517  $headlineCount++;
4518  }
4519 
4520  $this->setOutputType( $oldType );
4521 
4522  # Never ever show TOC if no headers
4523  if ( $numVisible < 1 ) {
4524  $enoughToc = false;
4525  }
4526 
4527  if ( $enoughToc ) {
4528  if ( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
4529  $toc .= Linker::tocUnindent( $prevtoclevel - 1 );
4530  }
4531  $toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
4532  $this->mOutput->setTOCHTML( $toc );
4533  $toc = self::TOC_START . $toc . self::TOC_END;
4534  }
4535 
4536  if ( $isMain ) {
4537  $this->mOutput->setSections( $tocraw );
4538  }
4539 
4540  # split up and insert constructed headlines
4541  $blocks = preg_split( '/<H[1-6].*?' . '>[\s\S]*?<\/H[1-6]>/i', $text );
4542  $i = 0;
4543 
4544  // build an array of document sections
4545  $sections = array();
4546  foreach ( $blocks as $block ) {
4547  // $head is zero-based, sections aren't.
4548  if ( empty( $head[$i - 1] ) ) {
4549  $sections[$i] = $block;
4550  } else {
4551  $sections[$i] = $head[$i - 1] . $block;
4552  }
4553 
4564  wfRunHooks( 'ParserSectionCreate', array( $this, $i, &$sections[$i], $showEditLink ) );
4565 
4566  $i++;
4567  }
4568 
4569  if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
4570  // append the TOC at the beginning
4571  // Top anchor now in skin
4572  $sections[0] = $sections[0] . $toc . "\n";
4573  }
4574 
4575  $full .= join( '', $sections );
4576 
4577  if ( $this->mForceTocPosition ) {
4578  return str_replace( '<!--MWTOC-->', $toc, $full );
4579  } else {
4580  return $full;
4581  }
4582  }
4583 
4595  public function preSaveTransform( $text, Title $title, User $user, ParserOptions $options, $clearState = true ) {
4596  $this->startParse( $title, $options, self::OT_WIKI, $clearState );
4597  $this->setUser( $user );
4598 
4599  $pairs = array(
4600  "\r\n" => "\n",
4601  );
4602  $text = str_replace( array_keys( $pairs ), array_values( $pairs ), $text );
4603  if ( $options->getPreSaveTransform() ) {
4604  $text = $this->pstPass2( $text, $user );
4605  }
4606  $text = $this->mStripState->unstripBoth( $text );
4607 
4608  $this->setUser( null ); #Reset
4609 
4610  return $text;
4611  }
4612 
4621  private function pstPass2( $text, $user ) {
4623 
4624  # Note: This is the timestamp saved as hardcoded wikitext to
4625  # the database, we use $wgContLang here in order to give
4626  # everyone the same signature and use the default one rather
4627  # than the one selected in each user's preferences.
4628  # (see also bug 12815)
4629  $ts = $this->mOptions->getTimestamp();
4631  $ts = $timestamp->format( 'YmdHis' );
4632  $tzMsg = $timestamp->format( 'T' ); # might vary on DST changeover!
4633 
4634  # Allow translation of timezones through wiki. format() can return
4635  # whatever crap the system uses, localised or not, so we cannot
4636  # ship premade translations.
4637  $key = 'timezone-' . strtolower( trim( $tzMsg ) );
4638  $msg = wfMessage( $key )->inContentLanguage();
4639  if ( $msg->exists() ) {
4640  $tzMsg = $msg->text();
4641  }
4642 
4643  $d = $wgContLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4644 
4645  # Variable replacement
4646  # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4647  $text = $this->replaceVariables( $text );
4648 
4649  # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4650  # which may corrupt this parser instance via its wfMessage()->text() call-
4651 
4652  # Signatures
4653  $sigText = $this->getUserSig( $user );
4654  $text = strtr( $text, array(
4655  '~~~~~' => $d,
4656  '~~~~' => "$sigText $d",
4657  '~~~' => $sigText
4658  ) );
4659 
4660  # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4661  $tc = '[' . Title::legalChars() . ']';
4662  $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4663 
4664  $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/"; # [[ns:page (context)|]]
4665  $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/"; # [[ns:page(context)|]] (double-width brackets, added in r40257)
4666  $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/"; # [[ns:page (context), context|]] (using either single or double-width comma)
4667  $p2 = "/\[\[\\|($tc+)]]/"; # [[|page]] (reverse pipe trick: add context from page title)
4668 
4669  # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4670  $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
4671  $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]', $text );
4672  $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
4673 
4674  $t = $this->mTitle->getText();
4675  $m = array();
4676  if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4677  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4678  } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4679  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4680  } else {
4681  # if there's no context, don't bother duplicating the title
4682  $text = preg_replace( $p2, '[[\\1]]', $text );
4683  }
4684 
4685  # Trim trailing whitespace
4686  $text = rtrim( $text );
4687 
4688  return $text;
4689  }
4690 
4705  function getUserSig( &$user, $nickname = false, $fancySig = null ) {
4706  global $wgMaxSigChars;
4707 
4708  $username = $user->getName();
4709 
4710  # If not given, retrieve from the user object.
4711  if ( $nickname === false ) {
4712  $nickname = $user->getOption( 'nickname' );
4713  }
4714 
4715  if ( is_null( $fancySig ) ) {
4716  $fancySig = $user->getBoolOption( 'fancysig' );
4717  }
4718 
4719  $nickname = $nickname == null ? $username : $nickname;
4720 
4721  if ( mb_strlen( $nickname ) > $wgMaxSigChars ) {
4722  $nickname = $username;
4723  wfDebug( __METHOD__ . ": $username has overlong signature.\n" );
4724  } elseif ( $fancySig !== false ) {
4725  # Sig. might contain markup; validate this
4726  if ( $this->validateSig( $nickname ) !== false ) {
4727  # Validated; clean up (if needed) and return it
4728  return $this->cleanSig( $nickname, true );
4729  } else {
4730  # Failed to validate; fall back to the default
4731  $nickname = $username;
4732  wfDebug( __METHOD__ . ": $username has bad XML tags in signature.\n" );
4733  }
4734  }
4735 
4736  # Make sure nickname doesnt get a sig in a sig
4737  $nickname = self::cleanSigInSig( $nickname );
4738 
4739  # If we're still here, make it a link to the user page
4740  $userText = wfEscapeWikiText( $username );
4741  $nickText = wfEscapeWikiText( $nickname );
4742  $msgName = $user->isAnon() ? 'signature-anon' : 'signature';
4743 
4744  return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()->title( $this->getTitle() )->text();
4745  }
4746 
4753  function validateSig( $text ) {
4754  return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
4755  }
4756 
4767  public function cleanSig( $text, $parsing = false ) {
4768  if ( !$parsing ) {
4769  global $wgTitle;
4770  $this->startParse( $wgTitle, new ParserOptions, self::OT_PREPROCESS, true );
4771  }
4772 
4773  # Option to disable this feature
4774  if ( !$this->mOptions->getCleanSignatures() ) {
4775  return $text;
4776  }
4777 
4778  # @todo FIXME: Regex doesn't respect extension tags or nowiki
4779  # => Move this logic to braceSubstitution()
4780  $substWord = MagicWord::get( 'subst' );
4781  $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
4782  $substText = '{{' . $substWord->getSynonym( 0 );
4783 
4784  $text = preg_replace( $substRegex, $substText, $text );
4785  $text = self::cleanSigInSig( $text );
4786  $dom = $this->preprocessToDom( $text );
4787  $frame = $this->getPreprocessor()->newFrame();
4788  $text = $frame->expand( $dom );
4789 
4790  if ( !$parsing ) {
4791  $text = $this->mStripState->unstripBoth( $text );
4792  }
4793 
4794  return $text;
4795  }
4796 
4803  public static function cleanSigInSig( $text ) {
4804  $text = preg_replace( '/~{3,5}/', '', $text );
4805  return $text;
4806  }
4807 
4817  public function startExternalParse( Title $title = null, ParserOptions $options, $outputType, $clearState = true ) {
4818  $this->startParse( $title, $options, $outputType, $clearState );
4819  }
4820 
4827  private function startParse( Title $title = null, ParserOptions $options, $outputType, $clearState = true ) {
4828  $this->setTitle( $title );
4829  $this->mOptions = $options;
4830  $this->setOutputType( $outputType );
4831  if ( $clearState ) {
4832  $this->clearState();
4833  }
4834  }
4835 
4844  public function transformMsg( $text, $options, $title = null ) {
4845  static $executing = false;
4846 
4847  # Guard against infinite recursion
4848  if ( $executing ) {
4849  return $text;
4850  }
4851  $executing = true;
4852 
4853  wfProfileIn( __METHOD__ );
4854  if ( !$title ) {
4855  global $wgTitle;
4856  $title = $wgTitle;
4857  }
4858 
4859  $text = $this->preprocess( $text, $title, $options );
4860 
4861  $executing = false;
4862  wfProfileOut( __METHOD__ );
4863  return $text;
4864  }
4865 
4890  public function setHook( $tag, $callback ) {
4891  $tag = strtolower( $tag );
4892  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4893  throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
4894  }
4895  $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null;
4896  $this->mTagHooks[$tag] = $callback;
4897  if ( !in_array( $tag, $this->mStripList ) ) {
4898  $this->mStripList[] = $tag;
4899  }
4900 
4901  return $oldVal;
4902  }
4903 
4921  function setTransparentTagHook( $tag, $callback ) {
4922  $tag = strtolower( $tag );
4923  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4924  throw new MWException( "Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
4925  }
4926  $oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] : null;
4927  $this->mTransparentTagHooks[$tag] = $callback;
4928 
4929  return $oldVal;
4930  }
4931 
4935  function clearTagHooks() {
4936  $this->mTagHooks = array();
4937  $this->mFunctionTagHooks = array();
4938  $this->mStripList = $this->mDefaultStripList;
4939  }
4940 
4984  public function setFunctionHook( $id, $callback, $flags = 0 ) {
4986 
4987  $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] : null;
4988  $this->mFunctionHooks[$id] = array( $callback, $flags );
4989 
4990  # Add to function cache
4991  $mw = MagicWord::get( $id );
4992  if ( !$mw ) {
4993  throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
4994  }
4995 
4996  $synonyms = $mw->getSynonyms();
4997  $sensitive = intval( $mw->isCaseSensitive() );
4998 
4999  foreach ( $synonyms as $syn ) {
5000  # Case
5001  if ( !$sensitive ) {
5002  $syn = $wgContLang->lc( $syn );
5003  }
5004  # Add leading hash
5005  if ( !( $flags & SFH_NO_HASH ) ) {
5006  $syn = '#' . $syn;
5007  }
5008  # Remove trailing colon
5009  if ( substr( $syn, -1, 1 ) === ':' ) {
5010  $syn = substr( $syn, 0, -1 );
5011  }
5012  $this->mFunctionSynonyms[$sensitive][$syn] = $id;
5013  }
5014  return $oldVal;
5015  }
5016 
5022  function getFunctionHooks() {
5023  return array_keys( $this->mFunctionHooks );
5024  }
5025 
5036  function setFunctionTagHook( $tag, $callback, $flags ) {
5037  $tag = strtolower( $tag );
5038  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
5039  throw new MWException( "Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
5040  }
5041  $old = isset( $this->mFunctionTagHooks[$tag] ) ?
5042  $this->mFunctionTagHooks[$tag] : null;
5043  $this->mFunctionTagHooks[$tag] = array( $callback, $flags );
5044 
5045  if ( !in_array( $tag, $this->mStripList ) ) {
5046  $this->mStripList[] = $tag;
5047  }
5048 
5049  return $old;
5050  }
5051 
5062  function replaceLinkHolders( &$text, $options = 0 ) {
5063  return $this->mLinkHolders->replace( $text );
5064  }
5065 
5073  function replaceLinkHoldersText( $text ) {
5074  return $this->mLinkHolders->replaceText( $text );
5075  }
5076 
5090  function renderImageGallery( $text, $params ) {
5091  wfProfileIn( __METHOD__ );
5092 
5093  $mode = false;
5094  if ( isset( $params['mode'] ) ) {
5095  $mode = $params['mode'];
5096  }
5097 
5098  try {
5099  $ig = ImageGalleryBase::factory( $mode );
5100  } catch ( MWException $e ) {
5101  // If invalid type set, fallback to default.
5102  $ig = ImageGalleryBase::factory( false );
5103  }
5104 
5105  $ig->setContextTitle( $this->mTitle );
5106  $ig->setShowBytes( false );
5107  $ig->setShowFilename( false );
5108  $ig->setParser( $this );
5109  $ig->setHideBadImages();
5110  $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'table' ) );
5111 
5112  if ( isset( $params['showfilename'] ) ) {
5113  $ig->setShowFilename( true );
5114  } else {
5115  $ig->setShowFilename( false );
5116  }
5117  if ( isset( $params['caption'] ) ) {
5118  $caption = $params['caption'];
5119  $caption = htmlspecialchars( $caption );
5120  $caption = $this->replaceInternalLinks( $caption );
5121  $ig->setCaptionHtml( $caption );
5122  }
5123  if ( isset( $params['perrow'] ) ) {
5124  $ig->setPerRow( $params['perrow'] );
5125  }
5126  if ( isset( $params['widths'] ) ) {
5127  $ig->setWidths( $params['widths'] );
5128  }
5129  if ( isset( $params['heights'] ) ) {
5130  $ig->setHeights( $params['heights'] );
5131  }
5132  $ig->setAdditionalOptions( $params );
5133 
5134  wfRunHooks( 'BeforeParserrenderImageGallery', array( &$this, &$ig ) );
5135 
5136  $lines = StringUtils::explode( "\n", $text );
5137  foreach ( $lines as $line ) {
5138  # match lines like these:
5139  # Image:someimage.jpg|This is some image
5140  $matches = array();
5141  preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
5142  # Skip empty lines
5143  if ( count( $matches ) == 0 ) {
5144  continue;
5145  }
5146 
5147  if ( strpos( $matches[0], '%' ) !== false ) {
5148  $matches[1] = rawurldecode( $matches[1] );
5149  }
5151  if ( is_null( $title ) ) {
5152  # Bogus title. Ignore these so we don't bomb out later.
5153  continue;
5154  }
5155 
5156  # We need to get what handler the file uses, to figure out parameters.
5157  # Note, a hook can overide the file name, and chose an entirely different
5158  # file (which potentially could be of a different type and have different handler).
5159  $options = array();
5160  $descQuery = false;
5161  wfRunHooks( 'BeforeParserFetchFileAndTitle',
5162  array( $this, $title, &$options, &$descQuery ) );
5163  # Don't register it now, as ImageGallery does that later.
5164  $file = $this->fetchFileNoRegister( $title, $options );
5165  $handler = $file ? $file->getHandler() : false;
5166 
5167  wfProfileIn( __METHOD__ . '-getMagicWord' );
5168  $paramMap = array(
5169  'img_alt' => 'gallery-internal-alt',
5170  'img_link' => 'gallery-internal-link',
5171  );
5172  if ( $handler ) {
5173  $paramMap = $paramMap + $handler->getParamMap();
5174  // We don't want people to specify per-image widths.
5175  // Additionally the width parameter would need special casing anyhow.
5176  unset( $paramMap['img_width'] );
5177  }
5178 
5179  $mwArray = new MagicWordArray( array_keys( $paramMap ) );
5180  wfProfileOut( __METHOD__ . '-getMagicWord' );
5181 
5182  $label = '';
5183  $alt = '';
5184  $link = '';
5185  $handlerOptions = array();
5186  if ( isset( $matches[3] ) ) {
5187  // look for an |alt= definition while trying not to break existing
5188  // captions with multiple pipes (|) in it, until a more sensible grammar
5189  // is defined for images in galleries
5190 
5191  // FIXME: Doing recursiveTagParse at this stage, and the trim before
5192  // splitting on '|' is a bit odd, and different from makeImage.
5193  $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
5194  $parameterMatches = StringUtils::explode( '|', $matches[3] );
5195 
5196  foreach ( $parameterMatches as $parameterMatch ) {
5197  list( $magicName, $match ) = $mwArray->matchVariableStartToEnd( $parameterMatch );
5198  if ( $magicName ) {
5199  $paramName = $paramMap[$magicName];
5200 
5201  switch ( $paramName ) {
5202  case 'gallery-internal-alt':
5203  $alt = $this->stripAltText( $match, false );
5204  break;
5205  case 'gallery-internal-link':
5206  $linkValue = strip_tags( $this->replaceLinkHoldersText( $match ) );
5207  $chars = self::EXT_LINK_URL_CLASS;
5208  $prots = $this->mUrlProtocols;
5209  //check to see if link matches an absolute url, if not then it must be a wiki link.
5210  if ( preg_match( "/^($prots)$chars+$/u", $linkValue ) ) {
5211  $link = $linkValue;
5212  $this->mOutput->addExternalLink( $link );
5213  } else {
5214  $localLinkTitle = Title::newFromText( $linkValue );
5215  if ( $localLinkTitle !== null ) {
5216  $this->mOutput->addLink( $localLinkTitle );
5217  $link = $localLinkTitle->getLinkURL();
5218  }
5219  }
5220  break;
5221  default:
5222  // Must be a handler specific parameter.
5223  if ( $handler->validateParam( $paramName, $match ) ) {
5224  $handlerOptions[$paramName] = $match;
5225  } else {
5226  // Guess not. Append it to the caption.
5227  wfDebug( "$parameterMatch failed parameter validation\n" );
5228  $label .= '|' . $parameterMatch;
5229  }
5230  }
5231 
5232  } else {
5233  // concatenate all other pipes
5234  $label .= '|' . $parameterMatch;
5235  }
5236  }
5237  // remove the first pipe
5238  $label = substr( $label, 1 );
5239  }
5240 
5241  $ig->add( $title, $label, $alt, $link, $handlerOptions );
5242  }
5243  $html = $ig->toHTML();
5244  wfProfileOut( __METHOD__ );
5245  return $html;
5246  }
5247 
5252  function getImageParams( $handler ) {
5253  if ( $handler ) {
5254  $handlerClass = get_class( $handler );
5255  } else {
5256  $handlerClass = '';
5257  }
5258  if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5259  # Initialise static lists
5260  static $internalParamNames = array(
5261  'horizAlign' => array( 'left', 'right', 'center', 'none' ),
5262  'vertAlign' => array( 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
5263  'bottom', 'text-bottom' ),
5264  'frame' => array( 'thumbnail', 'manualthumb', 'framed', 'frameless',
5265  'upright', 'border', 'link', 'alt', 'class' ),
5266  );
5267  static $internalParamMap;
5268  if ( !$internalParamMap ) {
5269  $internalParamMap = array();
5270  foreach ( $internalParamNames as $type => $names ) {
5271  foreach ( $names as $name ) {
5272  $magicName = str_replace( '-', '_', "img_$name" );
5273  $internalParamMap[$magicName] = array( $type, $name );
5274  }
5275  }
5276  }
5277 
5278  # Add handler params
5279  $paramMap = $internalParamMap;
5280  if ( $handler ) {
5281  $handlerParamMap = $handler->getParamMap();
5282  foreach ( $handlerParamMap as $magic => $paramName ) {
5283  $paramMap[$magic] = array( 'handler', $paramName );
5284  }
5285  }
5286  $this->mImageParams[$handlerClass] = $paramMap;
5287  $this->mImageParamsMagicArray[$handlerClass] = new MagicWordArray( array_keys( $paramMap ) );
5288  }
5289  return array( $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] );
5290  }
5291 
5300  function makeImage( $title, $options, $holders = false ) {
5301  # Check if the options text is of the form "options|alt text"
5302  # Options are:
5303  # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5304  # * left no resizing, just left align. label is used for alt= only
5305  # * right same, but right aligned
5306  # * none same, but not aligned
5307  # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5308  # * center center the image
5309  # * frame Keep original image size, no magnify-button.
5310  # * framed Same as "frame"
5311  # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5312  # * upright reduce width for upright images, rounded to full __0 px
5313  # * border draw a 1px border around the image
5314  # * alt Text for HTML alt attribute (defaults to empty)
5315  # * class Set a class for img node
5316  # * link Set the target of the image link. Can be external, interwiki, or local
5317  # vertical-align values (no % or length right now):
5318  # * baseline
5319  # * sub
5320  # * super
5321  # * top
5322  # * text-top
5323  # * middle
5324  # * bottom
5325  # * text-bottom
5326 
5327  $parts = StringUtils::explode( "|", $options );
5328 
5329  # Give extensions a chance to select the file revision for us
5330  $options = array();
5331  $descQuery = false;
5332  wfRunHooks( 'BeforeParserFetchFileAndTitle',
5333  array( $this, $title, &$options, &$descQuery ) );
5334  # Fetch and register the file (file title may be different via hooks)
5335  list( $file, $title ) = $this->fetchFileAndTitle( $title, $options );
5336 
5337  # Get parameter map
5338  $handler = $file ? $file->getHandler() : false;
5339 
5340  list( $paramMap, $mwArray ) = $this->getImageParams( $handler );
5341 
5342  if ( !$file ) {
5343  $this->addTrackingCategory( 'broken-file-category' );
5344  }
5345 
5346  # Process the input parameters
5347  $caption = '';
5348  $params = array( 'frame' => array(), 'handler' => array(),
5349  'horizAlign' => array(), 'vertAlign' => array() );
5350  foreach ( $parts as $part ) {
5351  $part = trim( $part );
5352  list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part );
5353  $validated = false;
5354  if ( isset( $paramMap[$magicName] ) ) {
5355  list( $type, $paramName ) = $paramMap[$magicName];
5356 
5357  # Special case; width and height come in one variable together
5358  if ( $type === 'handler' && $paramName === 'width' ) {
5359  $parsedWidthParam = $this->parseWidthParam( $value );
5360  if ( isset( $parsedWidthParam['width'] ) ) {
5361  $width = $parsedWidthParam['width'];
5362  if ( $handler->validateParam( 'width', $width ) ) {
5363  $params[$type]['width'] = $width;
5364  $validated = true;
5365  }
5366  }
5367  if ( isset( $parsedWidthParam['height'] ) ) {
5368  $height = $parsedWidthParam['height'];
5369  if ( $handler->validateParam( 'height', $height ) ) {
5370  $params[$type]['height'] = $height;
5371  $validated = true;
5372  }
5373  }
5374  # else no validation -- bug 13436
5375  } else {
5376  if ( $type === 'handler' ) {
5377  # Validate handler parameter
5378  $validated = $handler->validateParam( $paramName, $value );
5379  } else {
5380  # Validate internal parameters
5381  switch ( $paramName ) {
5382  case 'manualthumb':
5383  case 'alt':
5384  case 'class':
5385  # @todo FIXME: Possibly check validity here for
5386  # manualthumb? downstream behavior seems odd with
5387  # missing manual thumbs.
5388  $validated = true;
5389  $value = $this->stripAltText( $value, $holders );
5390  break;
5391  case 'link':
5392  $chars = self::EXT_LINK_URL_CLASS;
5393  $prots = $this->mUrlProtocols;
5394  if ( $value === '' ) {
5395  $paramName = 'no-link';
5396  $value = true;
5397  $validated = true;
5398  } elseif ( preg_match( "/^(?i)$prots/", $value ) ) {
5399  if ( preg_match( "/^((?i)$prots)$chars+$/u", $value, $m ) ) {
5400  $paramName = 'link-url';
5401  $this->mOutput->addExternalLink( $value );
5402  if ( $this->mOptions->getExternalLinkTarget() ) {
5403  $params[$type]['link-target'] = $this->mOptions->getExternalLinkTarget();
5404  }
5405  $validated = true;
5406  }
5407  } else {
5408  $linkTitle = Title::newFromText( $value );
5409  if ( $linkTitle ) {
5410  $paramName = 'link-title';
5411  $value = $linkTitle;
5412  $this->mOutput->addLink( $linkTitle );
5413  $validated = true;
5414  }
5415  }
5416  break;
5417  default:
5418  # Most other things appear to be empty or numeric...
5419  $validated = ( $value === false || is_numeric( trim( $value ) ) );
5420  }
5421  }
5422 
5423  if ( $validated ) {
5424  $params[$type][$paramName] = $value;
5425  }
5426  }
5427  }
5428  if ( !$validated ) {
5429  $caption = $part;
5430  }
5431  }
5432 
5433  # Process alignment parameters
5434  if ( $params['horizAlign'] ) {
5435  $params['frame']['align'] = key( $params['horizAlign'] );
5436  }
5437  if ( $params['vertAlign'] ) {
5438  $params['frame']['valign'] = key( $params['vertAlign'] );
5439  }
5440 
5441  $params['frame']['caption'] = $caption;
5442 
5443  # Will the image be presented in a frame, with the caption below?
5444  $imageIsFramed = isset( $params['frame']['frame'] )
5445  || isset( $params['frame']['framed'] )
5446  || isset( $params['frame']['thumbnail'] )
5447  || isset( $params['frame']['manualthumb'] );
5448 
5449  # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5450  # came to also set the caption, ordinary text after the image -- which
5451  # makes no sense, because that just repeats the text multiple times in
5452  # screen readers. It *also* came to set the title attribute.
5453  #
5454  # Now that we have an alt attribute, we should not set the alt text to
5455  # equal the caption: that's worse than useless, it just repeats the
5456  # text. This is the framed/thumbnail case. If there's no caption, we
5457  # use the unnamed parameter for alt text as well, just for the time be-
5458  # ing, if the unnamed param is set and the alt param is not.
5459  #
5460  # For the future, we need to figure out if we want to tweak this more,
5461  # e.g., introducing a title= parameter for the title; ignoring the un-
5462  # named parameter entirely for images without a caption; adding an ex-
5463  # plicit caption= parameter and preserving the old magic unnamed para-
5464  # meter for BC; ...
5465  if ( $imageIsFramed ) { # Framed image
5466  if ( $caption === '' && !isset( $params['frame']['alt'] ) ) {
5467  # No caption or alt text, add the filename as the alt text so
5468  # that screen readers at least get some description of the image
5469  $params['frame']['alt'] = $title->getText();
5470  }
5471  # Do not set $params['frame']['title'] because tooltips don't make sense
5472  # for framed images
5473  } else { # Inline image
5474  if ( !isset( $params['frame']['alt'] ) ) {
5475  # No alt text, use the "caption" for the alt text
5476  if ( $caption !== '' ) {
5477  $params['frame']['alt'] = $this->stripAltText( $caption, $holders );
5478  } else {
5479  # No caption, fall back to using the filename for the
5480  # alt text
5481  $params['frame']['alt'] = $title->getText();
5482  }
5483  }
5484  # Use the "caption" for the tooltip text
5485  $params['frame']['title'] = $this->stripAltText( $caption, $holders );
5486  }
5487 
5488  wfRunHooks( 'ParserMakeImageParams', array( $title, $file, &$params, $this ) );
5489 
5490  # Linker does the rest
5491  $time = isset( $options['time'] ) ? $options['time'] : false;
5492  $ret = Linker::makeImageLink( $this, $title, $file, $params['frame'], $params['handler'],
5493  $time, $descQuery, $this->mOptions->getThumbSize() );
5494 
5495  # Give the handler a chance to modify the parser object
5496  if ( $handler ) {
5497  $handler->parserTransformHook( $this, $file );
5498  }
5499 
5500  return $ret;
5501  }
5502 
5508  protected function stripAltText( $caption, $holders ) {
5509  # Strip bad stuff out of the title (tooltip). We can't just use
5510  # replaceLinkHoldersText() here, because if this function is called
5511  # from replaceInternalLinks2(), mLinkHolders won't be up-to-date.
5512  if ( $holders ) {
5513  $tooltip = $holders->replaceText( $caption );
5514  } else {
5515  $tooltip = $this->replaceLinkHoldersText( $caption );
5516  }
5517 
5518  # make sure there are no placeholders in thumbnail attributes
5519  # that are later expanded to html- so expand them now and
5520  # remove the tags
5521  $tooltip = $this->mStripState->unstripBoth( $tooltip );
5522  $tooltip = Sanitizer::stripAllTags( $tooltip );
5523 
5524  return $tooltip;
5525  }
5526 
5531  function disableCache() {
5532  wfDebug( "Parser output marked as uncacheable.\n" );
5533  if ( !$this->mOutput ) {
5534  throw new MWException( __METHOD__ .
5535  " can only be called when actually parsing something" );
5536  }
5537  $this->mOutput->setCacheTime( -1 ); // old style, for compatibility
5538  $this->mOutput->updateCacheExpiry( 0 ); // new style, for consistency
5539  }
5540 
5549  function attributeStripCallback( &$text, $frame = false ) {
5550  $text = $this->replaceVariables( $text, $frame );
5551  $text = $this->mStripState->unstripBoth( $text );
5552  return $text;
5553  }
5554 
5560  function getTags() {
5561  return array_merge( array_keys( $this->mTransparentTagHooks ), array_keys( $this->mTagHooks ), array_keys( $this->mFunctionTagHooks ) );
5562  }
5563 
5574  function replaceTransparentTags( $text ) {
5575  $matches = array();
5576  $elements = array_keys( $this->mTransparentTagHooks );
5577  $text = self::extractTagsAndParams( $elements, $text, $matches, $this->mUniqPrefix );
5578  $replacements = array();
5579 
5580  foreach ( $matches as $marker => $data ) {
5581  list( $element, $content, $params, $tag ) = $data;
5582  $tagName = strtolower( $element );
5583  if ( isset( $this->mTransparentTagHooks[$tagName] ) ) {
5584  $output = call_user_func_array( $this->mTransparentTagHooks[$tagName], array( $content, $params, $this ) );
5585  } else {
5586  $output = $tag;
5587  }
5588  $replacements[$marker] = $output;
5589  }
5590  return strtr( $text, $replacements );
5591  }
5592 
5622  private function extractSections( $text, $section, $mode, $newText = '' ) {
5623  global $wgTitle; # not generally used but removes an ugly failure mode
5624  $this->startParse( $wgTitle, new ParserOptions, self::OT_PLAIN, true );
5625  $outText = '';
5626  $frame = $this->getPreprocessor()->newFrame();
5627 
5628  # Process section extraction flags
5629  $flags = 0;
5630  $sectionParts = explode( '-', $section );
5631  $sectionIndex = array_pop( $sectionParts );
5632  foreach ( $sectionParts as $part ) {
5633  if ( $part === 'T' ) {
5634  $flags |= self::PTD_FOR_INCLUSION;
5635  }
5636  }
5637 
5638  # Check for empty input
5639  if ( strval( $text ) === '' ) {
5640  # Only sections 0 and T-0 exist in an empty document
5641  if ( $sectionIndex == 0 ) {
5642  if ( $mode === 'get' ) {
5643  return '';
5644  } else {
5645  return $newText;
5646  }
5647  } else {
5648  if ( $mode === 'get' ) {
5649  return $newText;
5650  } else {
5651  return $text;
5652  }
5653  }
5654  }
5655 
5656  # Preprocess the text
5657  $root = $this->preprocessToDom( $text, $flags );
5658 
5659  # <h> nodes indicate section breaks
5660  # They can only occur at the top level, so we can find them by iterating the root's children
5661  $node = $root->getFirstChild();
5662 
5663  # Find the target section
5664  if ( $sectionIndex == 0 ) {
5665  # Section zero doesn't nest, level=big
5666  $targetLevel = 1000;
5667  } else {
5668  while ( $node ) {
5669  if ( $node->getName() === 'h' ) {
5670  $bits = $node->splitHeading();
5671  if ( $bits['i'] == $sectionIndex ) {
5672  $targetLevel = $bits['level'];
5673  break;
5674  }
5675  }
5676  if ( $mode === 'replace' ) {
5677  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5678  }
5679  $node = $node->getNextSibling();
5680  }
5681  }
5682 
5683  if ( !$node ) {
5684  # Not found
5685  if ( $mode === 'get' ) {
5686  return $newText;
5687  } else {
5688  return $text;
5689  }
5690  }
5691 
5692  # Find the end of the section, including nested sections
5693  do {
5694  if ( $node->getName() === 'h' ) {
5695  $bits = $node->splitHeading();
5696  $curLevel = $bits['level'];
5697  if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5698  break;
5699  }
5700  }
5701  if ( $mode === 'get' ) {
5702  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5703  }
5704  $node = $node->getNextSibling();
5705  } while ( $node );
5706 
5707  # Write out the remainder (in replace mode only)
5708  if ( $mode === 'replace' ) {
5709  # Output the replacement text
5710  # Add two newlines on -- trailing whitespace in $newText is conventionally
5711  # stripped by the editor, so we need both newlines to restore the paragraph gap
5712  # Only add trailing whitespace if there is newText
5713  if ( $newText != "" ) {
5714  $outText .= $newText . "\n\n";
5715  }
5716 
5717  while ( $node ) {
5718  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5719  $node = $node->getNextSibling();
5720  }
5721  }
5722 
5723  if ( is_string( $outText ) ) {
5724  # Re-insert stripped tags
5725  $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5726  }
5727 
5728  return $outText;
5729  }
5730 
5743  public function getSection( $text, $section, $deftext = '' ) {
5744  return $this->extractSections( $text, $section, "get", $deftext );
5745  }
5746 
5757  public function replaceSection( $oldtext, $section, $text ) {
5758  return $this->extractSections( $oldtext, $section, "replace", $text );
5759  }
5760 
5766  function getRevisionId() {
5767  return $this->mRevisionId;
5768  }
5769 
5776  public function getRevisionObject() {
5777  if ( !is_null( $this->mRevisionObject ) ) {
5778  return $this->mRevisionObject;
5779  }
5780  if ( is_null( $this->mRevisionId ) ) {
5781  return null;
5782  }
5783 
5784  $this->mRevisionObject = Revision::newFromId( $this->mRevisionId );
5785  return $this->mRevisionObject;
5786  }
5787 
5792  function getRevisionTimestamp() {
5793  if ( is_null( $this->mRevisionTimestamp ) ) {
5794  wfProfileIn( __METHOD__ );
5795 
5797 
5798  $revObject = $this->getRevisionObject();
5799  $timestamp = $revObject ? $revObject->getTimestamp() : wfTimestampNow();
5800 
5801  # The cryptic '' timezone parameter tells to use the site-default
5802  # timezone offset instead of the user settings.
5803  #
5804  # Since this value will be saved into the parser cache, served
5805  # to other users, and potentially even used inside links and such,
5806  # it needs to be consistent for all visitors.
5807  $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
5808 
5809  wfProfileOut( __METHOD__ );
5810  }
5811  return $this->mRevisionTimestamp;
5812  }
5813 
5819  function getRevisionUser() {
5820  if ( is_null( $this->mRevisionUser ) ) {
5821  $revObject = $this->getRevisionObject();
5822 
5823  # if this template is subst: the revision id will be blank,
5824  # so just use the current user's name
5825  if ( $revObject ) {
5826  $this->mRevisionUser = $revObject->getUserText();
5827  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
5828  $this->mRevisionUser = $this->getUser()->getName();
5829  }
5830  }
5831  return $this->mRevisionUser;
5832  }
5833 
5839  function getRevisionSize() {
5840  if ( is_null( $this->mRevisionSize ) ) {
5841  $revObject = $this->getRevisionObject();
5842 
5843  # if this variable is subst: the revision id will be blank,
5844  # so just use the parser input size, because the own substituation
5845  # will change the size.
5846  if ( $revObject ) {
5847  $this->mRevisionSize = $revObject->getSize();
5848  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
5849  $this->mRevisionSize = $this->mInputSize;
5850  }
5851  }
5852  return $this->mRevisionSize;
5853  }
5854 
5860  public function setDefaultSort( $sort ) {
5861  $this->mDefaultSort = $sort;
5862  $this->mOutput->setProperty( 'defaultsort', $sort );
5863  }
5864 
5875  public function getDefaultSort() {
5876  if ( $this->mDefaultSort !== false ) {
5877  return $this->mDefaultSort;
5878  } else {
5879  return '';
5880  }
5881  }
5882 
5889  public function getCustomDefaultSort() {
5890  return $this->mDefaultSort;
5891  }
5892 
5902  public function guessSectionNameFromWikiText( $text ) {
5903  # Strip out wikitext links(they break the anchor)
5904  $text = $this->stripSectionName( $text );
5906  return '#' . Sanitizer::escapeId( $text, 'noninitial' );
5907  }
5908 
5917  public function guessLegacySectionNameFromWikiText( $text ) {
5918  # Strip out wikitext links(they break the anchor)
5919  $text = $this->stripSectionName( $text );
5921  return '#' . Sanitizer::escapeId( $text, array( 'noninitial', 'legacy' ) );
5922  }
5923 
5938  public function stripSectionName( $text ) {
5939  # Strip internal link markup
5940  $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
5941  $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
5942 
5943  # Strip external link markup
5944  # @todo FIXME: Not tolerant to blank link text
5945  # I.E. [https://www.mediawiki.org] will render as [1] or something depending
5946  # on how many empty links there are on the page - need to figure that out.
5947  $text = preg_replace( '/\[(?i:' . $this->mUrlProtocols . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
5948 
5949  # Parse wikitext quotes (italics & bold)
5950  $text = $this->doQuotes( $text );
5951 
5952  # Strip HTML tags
5953  $text = StringUtils::delimiterReplace( '<', '>', '', $text );
5954  return $text;
5955  }
5956 
5967  function testSrvus( $text, Title $title, ParserOptions $options, $outputType = self::OT_HTML ) {
5968  $this->startParse( $title, $options, $outputType, true );
5969 
5970  $text = $this->replaceVariables( $text );
5971  $text = $this->mStripState->unstripBoth( $text );
5972  $text = Sanitizer::removeHTMLtags( $text );
5973  return $text;
5974  }
5975 
5982  function testPst( $text, Title $title, ParserOptions $options ) {
5983  return $this->preSaveTransform( $text, $title, $options->getUser(), $options );
5984  }
5985 
5992  function testPreprocess( $text, Title $title, ParserOptions $options ) {
5993  return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
5994  }
5995 
6012  function markerSkipCallback( $s, $callback ) {
6013  $i = 0;
6014  $out = '';
6015  while ( $i < strlen( $s ) ) {
6016  $markerStart = strpos( $s, $this->mUniqPrefix, $i );
6017  if ( $markerStart === false ) {
6018  $out .= call_user_func( $callback, substr( $s, $i ) );
6019  break;
6020  } else {
6021  $out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) );
6022  $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
6023  if ( $markerEnd === false ) {
6024  $out .= substr( $s, $markerStart );
6025  break;
6026  } else {
6027  $markerEnd += strlen( self::MARKER_SUFFIX );
6028  $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
6029  $i = $markerEnd;
6030  }
6031  }
6032  }
6033  return $out;
6034  }
6035 
6042  function killMarkers( $text ) {
6043  return $this->mStripState->killMarkers( $text );
6044  }
6045 
6062  function serializeHalfParsedText( $text ) {
6063  wfProfileIn( __METHOD__ );
6064  $data = array(
6065  'text' => $text,
6066  'version' => self::HALF_PARSED_VERSION,
6067  'stripState' => $this->mStripState->getSubState( $text ),
6068  'linkHolders' => $this->mLinkHolders->getSubArray( $text )
6069  );
6070  wfProfileOut( __METHOD__ );
6071  return $data;
6072  }
6073 
6089  function unserializeHalfParsedText( $data ) {
6090  if ( !isset( $data['version'] ) || $data['version'] != self::HALF_PARSED_VERSION ) {
6091  throw new MWException( __METHOD__ . ': invalid version' );
6092  }
6093 
6094  # First, extract the strip state.
6095  $texts = array( $data['text'] );
6096  $texts = $this->mStripState->merge( $data['stripState'], $texts );
6097 
6098  # Now renumber links
6099  $texts = $this->mLinkHolders->mergeForeign( $data['linkHolders'], $texts );
6100 
6101  # Should be good to go.
6102  return $texts[0];
6103  }
6104 
6114  function isValidHalfParsedText( $data ) {
6115  return isset( $data['version'] ) && $data['version'] == self::HALF_PARSED_VERSION;
6116  }
6117 
6126  public function parseWidthParam( $value ) {
6127  $parsedWidthParam = array();
6128  if ( $value === '' ) {
6129  return $parsedWidthParam;
6130  }
6131  $m = array();
6132  # (bug 13500) In both cases (width/height and width only),
6133  # permit trailing "px" for backward compatibility.
6134  if ( preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
6135  $width = intval( $m[1] );
6136  $height = intval( $m[2] );
6137  $parsedWidthParam['width'] = $width;
6138  $parsedWidthParam['height'] = $height;
6139  } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) {
6140  $width = intval( $value );
6141  $parsedWidthParam['width'] = $width;
6142  }
6143  return $parsedWidthParam;
6144  }
6145 }
OT_MSG
const OT_MSG
Definition: Defines.php:233
SiteStats\articles
static articles()
Definition: SiteStats.php:124
ParserOptions
Set options of the Parser.
Definition: ParserOptions.php:31
Title\makeTitle
static & makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:398
save
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a save
Definition: deferred.txt:4
MagicWordArray
Class for handling an array of magic words.
Definition: MagicWord.php:676
object
globals will be eliminated from MediaWiki replaced by an application object which would be passed to constructors Whether that would be an convenient solution remains to be but certainly PHP makes such object oriented programming models easier than they were in previous versions For the time being MediaWiki programmers will have to work in an environment with some global context At the time of globals were initialised on startup by MediaWiki of these were configuration which are documented in DefaultSettings php There is no comprehensive documentation for the remaining however some of the most important ones are listed below They are typically initialised either in index php or in Setup php For a description of the see design txt $wgTitle Title object created from the request URL $wgOut OutputPage object for HTTP response $wgUser User object for the user associated with the current request $wgLang Language object selected by user preferences $wgContLang Language object associated with the wiki being viewed $wgParser Parser object Parser extensions register their hooks here $wgRequest WebRequest object
Definition: globals.txt:25
$result
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. $reader:XMLReader object $logInfo:Array of information Return false to stop further processing of the tag 'ImportHandlePageXMLTag':When parsing a XML tag in a page. $reader:XMLReader object $pageInfo:Array of information Return false to stop further processing of the tag 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information Return false to stop further processing of the tag 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. $reader:XMLReader object Return false to stop further processing of the tag 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. $reader:XMLReader object $revisionInfo:Array of information Return false to stop further processing of the tag 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. $title:Title object for the current page $request:WebRequest $ignoreRedirect:boolean to skip redirect check $target:Title/string of redirect target $article:Article object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) $article:article(object) being checked 'IsTrustedProxy':Override the result of wfIsTrustedProxy() $ip:IP being check $result:Change this value to override the result of wfIsTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of User::isValidEmailAddr(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetMagic':DEPRECATED, use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetSpecialPageAliases':DEPRECATED, use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Associative array mapping language codes to prefixed links of the form "language:title". & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LinkBegin':Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1528
FauxRequest
WebRequest clone which takes values from a provided array.
Definition: WebRequest.php:1275
ID
occurs before session is loaded can be modified ID
Definition: hooks.txt:2829
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:189
PPFrame\STRIP_COMMENTS
const STRIP_COMMENTS
Definition: Preprocessor.php:75
DB_MASTER
const DB_MASTER
Definition: Defines.php:56
MWNamespace\isNonincludable
static isNonincludable( $index)
It is not possible to use pages from this namespace as template?
Definition: Namespace.php:417
of
globals txt Globals are evil The original MediaWiki code relied on globals for processing context far too often MediaWiki development since then has been a story of slowly moving context out of global variables and into objects Storing processing context in object member variables allows those objects to be reused in a much more flexible way Consider the elegance of
Definition: globals.txt:10
RepoGroup\singleton
static singleton()
Get a RepoGroup instance.
Definition: RepoGroup.php:53
ParserOutput
Definition: ParserOutput.php:24
php
skin txt MediaWiki includes four core it has been set as the default in MediaWiki since the replacing Monobook it had been been the default skin since before being replaced by Vector largely rewritten in while keeping its appearance Several legacy skins were removed in the as the burden of supporting them became too heavy to bear Those in etc for skin dependent CSS etc for skin dependent JavaScript These can also be customised on a per user by etc This feature has led to a wide variety of user styles becoming that gallery is a good place to ending in php
Definition: skin.txt:62
Revision\newFromId
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:88
SiteStats\users
static users()
Definition: SiteStats.php:140
$html
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
Definition: hooks.txt:1530
SiteStats\activeUsers
static activeUsers()
Definition: SiteStats.php:148
Profiler\instance
static instance()
Singleton.
Definition: Profiler.php:127
is
We use the convention $dbr for read and $dbw for write to help you keep track of whether the database object is a the world will explode Or to be a subsequent write query which succeeded on the master may fail when replicated to the slave due to a unique key collision Replication on the slave will stop and it may take hours to repair the database and get it back online Setting read_only in my cnf on the slave will avoid this but given the dire we prefer to have as many checks as possible We provide a but the wrapper functions like please read the documentation for except in special pages derived from QueryPage It s a common pitfall for new developers to submit code containing SQL queries which examine huge numbers of rows Remember that COUNT * is(N), counting rows in atable is like counting beans in a bucket.------------------------------------------------------------------------ Replication------------------------------------------------------------------------The largest installation of MediaWiki, Wikimedia, uses a large set ofslave MySQL servers replicating writes made to a master MySQL server. Itis important to understand the issues associated with this setup if youwant to write code destined for Wikipedia.It 's often the case that the best algorithm to use for a given taskdepends on whether or not replication is in use. Due to our unabashedWikipedia-centrism, we often just use the replication-friendly version, but if you like, you can use wfGetLB() ->getServerCount() > 1 tocheck to see if replication is in use.===Lag===Lag primarily occurs when large write queries are sent to the master.Writes on the master are executed in parallel, but they are executed inserial when they are replicated to the slaves. The master writes thequery to the binlog when the transaction is committed. The slaves pollthe binlog and start executing the query as soon as it appears. They canservice reads while they are performing a write query, but will not readanything more from the binlog and thus will perform no more writes. Thismeans that if the write query runs for a long time, the slaves will lagbehind the master for the time it takes for the write query to complete.Lag can be exacerbated by high read load. MediaWiki 's load balancer willstop sending reads to a slave when it is lagged by more than 30 seconds.If the load ratios are set incorrectly, or if there is too much loadgenerally, this may lead to a slave permanently hovering around 30seconds lag.If all slaves are lagged by more than 30 seconds, MediaWiki will stopwriting to the database. All edits and other write operations will berefused, with an error returned to the user. This gives the slaves achance to catch up. Before we had this mechanism, the slaves wouldregularly lag by several minutes, making review of recent editsdifficult.In addition to this, MediaWiki attempts to ensure that the user seesevents occurring on the wiki in chronological order. A few seconds of lagcan be tolerated, as long as the user sees a consistent picture fromsubsequent requests. This is done by saving the master binlog positionin the session, and then at the start of each request, waiting for theslave to catch up to that position before doing any reads from it. Ifthis wait times out, reads are allowed anyway, but the request isconsidered to be in "lagged slave mode". Lagged slave mode can bechecked by calling wfGetLB() ->getLaggedSlaveMode(). The onlypractical consequence at present is a warning displayed in the pagefooter.===Lag avoidance===To avoid excessive lag, queries which write large numbers of rows shouldbe split up, generally to write one row at a time. Multi-row INSERT ...SELECT queries are the worst offenders should be avoided altogether.Instead do the select first and then the insert.===Working with lag===Despite our best efforts, it 's not practical to guarantee a low-lagenvironment. Lag will usually be less than one second, but mayoccasionally be up to 30 seconds. For scalability, it 's very importantto keep load on the master low, so simply sending all your queries tothe master is not the answer. So when you have a genuine need forup-to-date data, the following approach is advised:1) Do a quick query to the master for a sequence number or timestamp 2) Run the full query on the slave and check if it matches the data you gotfrom the master 3) If it doesn 't, run the full query on the masterTo avoid swamping the master every time the slaves lag, use of thisapproach should be kept to a minimum. In most cases you should just readfrom the slave and let the user deal with the delay.------------------------------------------------------------------------ Lock contention------------------------------------------------------------------------Due to the high write rate on Wikipedia(and some other wikis), MediaWiki developers need to be very careful to structure their writesto avoid long-lasting locks. By default, MediaWiki opens a transactionat the first query, and commits it before the output is sent. Locks willbe held from the time when the query is done until the commit. So youcan reduce lock time by doing as much processing as possible before youdo your write queries.Often this approach is not good enough, and it becomes necessary toenclose small groups of queries in their own transaction. Use thefollowing syntax:$dbw=wfGetDB(DB_MASTER
Linker\makeSelfLinkObj
static makeSelfLinkObj( $nt, $html='', $query='', $trail='', $prefix='')
Make appropriate markup for a link to the current article.
Definition: Linker.php:409
PPFrame\NO_ARGS
const NO_ARGS
Definition: Preprocessor.php:73
wfSetVar
wfSetVar(&$dest, $source, $force=false)
Sets dest to source and returns the original value of dest If source is NULL, it just returns the val...
Definition: GlobalFunctions.php:2186
Linker\tocIndent
static tocIndent()
Add another level to the Table of Contents.
Definition: Linker.php:1624
wfGetDB
& wfGetDB( $db, $groups=array(), $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:3714
text
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:12
$timestamp
if( $limit) $timestamp
Definition: importImages.php:104
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:2530
wiki
Prior to maintenance scripts were a hodgepodge of code that had no cohesion or formal method of action Beginning maintenance scripts have been cleaned up to use a unified class Directory structure How to run a script How to write your own DIRECTORY STRUCTURE The maintenance directory of a MediaWiki installation contains several all of which have unique purposes HOW TO RUN A SCRIPT Ridiculously just call php someScript php that s in the top level maintenance directory if not default wiki
Definition: maintenance.txt:1
SiteStats\pages
static pages()
Definition: SiteStats.php:132
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all')
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1087
wfProfileIn
wfProfileIn( $functionname)
Begin profiling of a function.
Definition: Profiler.php:33
$n
$n
Definition: RandomTest.php:76
$ret
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:1530
wfUrlencode
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness,...
Definition: GlobalFunctions.php:377
SiteStats\numberingroup
static numberingroup( $group)
Find the number of users in a given user group.
Definition: SiteStats.php:166
$time
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1358
normal
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same so they can t rely on Unix and must forbid reads to even standard directories like tmp lest users read each others files We cannot assume that the user has the ability to install or run any programs not written as web accessible PHP scripts Since anything that works on cheap shared hosting will work if you have shell or root access MediaWiki s design is based around catering to the lowest common denominator Although we support higher end setups as the way many things work by default is tailored toward shared hosting These defaults are unconventional from the point of view of normal(non-web) applications -- they might conflict with distributors' policies
SFH_OBJECT_ARGS
const SFH_OBJECT_ARGS
Definition: Defines.php:241
MagicWord\get
static & get( $id)
Factory: creates an object representing an ID.
Definition: MagicWord.php:238
$fname
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined.
Definition: Setup.php:35
Sanitizer\normalizeSectionNameWhitespace
static normalizeSectionNameWhitespace( $section)
Normalizes whitespace in a section name, such as might be returned by Parser::stripSectionName(),...
Definition: Sanitizer.php:1298
OT_PREPROCESS
const OT_PREPROCESS
Definition: Defines.php:232
NS_FILE
const NS_FILE
Definition: Defines.php:85
ImageGalleryBase\factory
static factory( $mode=false)
Get a new image gallery.
Definition: ImageGalleryBase.php:66
OT_PLAIN
const OT_PLAIN
Definition: Defines.php:234
$params
$params
Definition: styleTest.css.php:40
$limit
if( $sleep) $limit
Definition: importImages.php:99
NS_TEMPLATE
const NS_TEMPLATE
Definition: Defines.php:89
$s
$s
Definition: mergeMessageFileList.php:156
SpecialPage\getTitleFor
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name.
Definition: SpecialPage.php:74
$wgContLang
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the content language as $wgContLang
Definition: design.txt:56
StripState
Definition: StripState.php:28
Makefile.open
open
Definition: Makefile.py:14
$flags
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2124
$link
set to $title object and return false for a match for latest after cache objects are set use the ContentHandler facility to handle CSS and JavaScript for highlighting & $link
Definition: hooks.txt:2160
cache
you have access to all of the normal MediaWiki so you can get a DB use the cache
Definition: maintenance.txt:52
Linker\tocLine
static tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition: Linker.php:1642
Sanitizer\stripAllTags
static stripAllTags( $text)
Take a fragment of (potentially invalid) HTML and return a version with any tags removed,...
Definition: Sanitizer.php:1736
FakeTitle
Fake title class that triggers an error if any members are called.
Definition: FakeTitle.php:26
Linker\linkKnown
static linkKnown( $target, $html=null, $customAttribs=array(), $query=array(), $options=array( 'known', 'noclasses'))
Identical to link(), except $options defaults to 'known'.
Definition: Linker.php:264
pre
</p > ! end ! test Empty pre
Definition: parserTests.txt:1588
Linker\makeExternalLink
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=array(), $title=null)
Make an external link.
Definition: Linker.php:1034
PPFrame\NO_TEMPLATES
const NO_TEMPLATES
Definition: Preprocessor.php:74
later
If you want to remove the page from your watchlist later
Definition: All_system_messages.txt:361
$dbr
$dbr
Definition: testCompression.php:48
SiteStats\images
static images()
Definition: SiteStats.php:156
Revision
Definition: Revision.php:26
key
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling but I prefer the flexibility This should also do the output encoding The system allocates a global one in $wgOut Title Represents the title of an and does all the work of translating among various forms such as plain database key
Definition: design.txt:25
title
to move a page</td >< td > &*You are moving the page across *A non empty talk page already exists under the new or *You uncheck the box below In those you will have to move or merge the page manually if desired</td >< td > be sure to &You are responsible for making sure that links continue to point where they are supposed to go Note that the page will &a page at the new title
Definition: All_system_messages.txt:2703
StringUtils\explodeMarkup
static explodeMarkup( $separator, $text)
More or less "markup-safe" explode() Ignores any instances of the separator inside <....
Definition: StringUtils.php:270
NS_SPECIAL
const NS_SPECIAL
Definition: Defines.php:68
RequestContext\setTitle
setTitle( $t)
Set the Title object.
Definition: RequestContext.php:116
wfParseUrl
wfParseUrl( $url)
parse_url() work-alike, but non-broken.
Definition: GlobalFunctions.php:802
MagicWord\getVariableIDs
static getVariableIDs()
Get an array of parser variable IDs.
Definition: MagicWord.php:252
MWException
MediaWiki exception.
Definition: MWException.php:26
$out
$out
Definition: UtfNormalGenerate.php:167
MagicWord\getCacheTTL
static getCacheTTL( $id)
Allow external reads of TTL array.
Definition: MagicWord.php:275
table
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global then executing the whole list after the page is displayed We don t do anything smart like collating updates to the same table or such because the list is almost always going to have just one item on if so it s not worth the trouble Since there is a job queue in the jobs table
Definition: deferred.txt:11
so
I won t presume to tell you how to I m just describing the methods I chose to use for myself If you do choose to follow these it will probably be easier for you to collaborate with others on the but if you want to contribute without by all means do so(and don 't be surprised if I reformat your code). - I have the code indented with tabs to save file size and so that users can set their tab stops to any depth they like. I use 4-space tab stops
Html\element
static element( $element, $attribs=array(), $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:141
there
has been added to your &Future changes to this page and its associated Talk page will be listed there
Definition: All_system_messages.txt:357
wfUrlProtocolsWithoutProtRel
wfUrlProtocolsWithoutProtRel()
Like wfUrlProtocols(), but excludes '//' from the protocol list.
Definition: GlobalFunctions.php:787
Linker\tocList
static tocList( $toc, $lang=false)
Wraps the TOC in a table and provides the hide/collapse javascript.
Definition: Linker.php:1670
$parser
do that in ParserLimitReportFormat instead $parser
Definition: hooks.txt:1961
CoreTagHooks\register
static register( $parser)
Definition: CoreTagHooks.php:33
directly
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling but I prefer the flexibility This should also do the output encoding The system allocates a global one in $wgOut Title Represents the title of an and does all the work of translating among various forms such as plain database etc For and for historical it also represents a few features of articles that don t involve their such as access rights See also title txt Article Encapsulates access to the page table of the database The object represents a an and maintains state such as etc Revision Encapsulates individual page revision data and access to the revision text blobs storage system Higher level code should never touch text storage directly
Definition: design.txt:34
StringUtils\explode
static explode( $separator, $subject)
Workalike for explode() with limited memory usage.
Definition: StringUtils.php:310
PPNode
There are three types of nodes:
Definition: Preprocessor.php:183
LinkHolderArray
Definition: LinkHolderArray.php:27
PPFrame\RECOVER_ORIG
const RECOVER_ORIG
Definition: Preprocessor.php:79
wfProfileOut
wfProfileOut( $functionname='missing')
Stop profiling of a function.
Definition: Profiler.php:46
wfMessage
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing after in associative array form externallinks including delete and has completed for all link tables default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
Linker\tocLineEnd
static tocLineEnd()
End a Table Of Contents line.
Definition: Linker.php:1659
wfRunHooks
wfRunHooks( $event, array $args=array(), $deprecatedVersion=null)
Call hook functions defined in $wgHooks.
Definition: GlobalFunctions.php:4066
MWNamespace\hasSubpages
static hasSubpages( $index)
Does the namespace allow subpages?
Definition: Namespace.php:325
wfCgiToArray
wfCgiToArray( $query)
This is the logical opposite of wfArrayToCgi(): it accepts a query string as its argument and returns...
Definition: GlobalFunctions.php:459
$lines
$lines
Definition: router.php:65
array
the array() calling protocol came about after MediaWiki 1.4rc1.
List of Api Query prop modules.
MWTimestamp\getInstance
static getInstance( $ts=false)
Get a timestamp instance in GMT.
Definition: MWTimestamp.php:387
add
An extension or a will often add custom code to the function with or without a global variable For someone wanting email notification when an article is shown may add
Definition: hooks.txt:51
OT_WIKI
const OT_WIKI
Definition: Defines.php:231
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
form
null means default in associative array form
Definition: hooks.txt:1530
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:2561
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:93
RequestContext
Group all the pieces relevant to the context of a request into one instance.
Definition: RequestContext.php:30
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:188
$sort
$sort
Definition: profileinfo.php:301
Linker\splitTrail
static splitTrail( $trail)
Split a link trail, return the "inside" portion and the remainder of the trail as a two-element array...
Definition: Linker.php:1740
Sanitizer\escapeId
static escapeId( $id, $options=array())
Given a value, escape it so that it can be used in an id attribute and return it.
Definition: Sanitizer.php:1100
$options
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1530
$section
$section
Definition: Utf8Test.php:88
wfUrlProtocols
wfUrlProtocols( $includeProtocolRelative=true)
Returns a regular expression of url protocols.
Definition: GlobalFunctions.php:742
$line
$line
Definition: cdb.php:57
TS_MW
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
Definition: GlobalFunctions.php:2478
wfDebug
wfDebug( $text, $dest='all')
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:980
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:422
$title
presenting them properly to the user as errors is done by the caller $title
Definition: hooks.txt:1324
CoreParserFunctions\register
static register( $parser)
Definition: CoreParserFunctions.php:33
see
Some information about database access in MediaWiki By Tim January Database layout For information about the MediaWiki database such as a description of the tables and their please see
Definition: database.txt:2
gt
b→ & gt
Definition: parserTests.txt:893
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:336
$matches
if(!defined( 'MEDIAWIKI')) if(!isset( $wgVersion)) $matches
Definition: NoLocalSettings.php:33
$size
$size
Definition: RandomTest.php:75
$value
$value
Definition: styleTest.css.php:45
NS_MEDIA
const NS_MEDIA
Definition: Defines.php:67
variable
controlled by $wgMainCacheType controlled by $wgParserCacheType controlled by $wgMessageCacheType If you set CACHE_NONE to one of the three control variable
Definition: memcached.txt:78
Sanitizer\validateTagAttributes
static validateTagAttributes( $attribs, $element)
Take an array of attribute names and values and normalize or discard illegal values for the given ele...
Definition: Sanitizer.php:691
PPFrame
Definition: Preprocessor.php:72
up
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title after the basic globals have been set up
Definition: hooks.txt:1684
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:2124
Linker\makeHeadline
static makeHeadline( $level, $attribs, $anchor, $html, $link, $legacyAnchor=false)
Create a headline for content.
Definition: Linker.php:1724
SpecialPageFactory\capturePath
static capturePath(Title $title, IContextSource $context)
Just like executePath() but will override global variables and execute the page in "inclusion" mode.
Definition: SpecialPageFactory.php:525
Sanitizer\cleanUrl
static cleanUrl( $url)
Definition: Sanitizer.php:1769
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:237
tags
pre inside other HTML tags(bug 54946) !! wikitext a< div >< pre > foo</pre ></div >< pre ></pre > !! html< p >a</p >< div >< pre > foo</pre ></div >< pre ></pre > !! end !! test HTML pre followed by indent-pre !! wikitext< pre >foo</pre > bar !! html< pre >foo</pre >< pre >bar</pre > !! end !!test Block tag pre !!options parsoid !! wikitext< p >< pre >foo</pre ></p > !! html< p data-parsoid
only
published in in Madrid In the first edition of the Vocabolario for was published In in Rotterdam was the Dictionnaire Universel ! html< p > The first monolingual dictionary written in a Romance language was< i > Sebastián Covarrubias</i >< i > Tesoro de la lengua castellana o published in in Madrid In the first edition of the< i > Vocabolario dell< a href="/index.php?title=Accademia_della_Crusca&amp;action=edit&amp;redlink=1" class="new" title="Accademia della Crusca (page does not exist)"> Accademia della Crusca</a ></i > for was published In in Rotterdam was the< i > Dictionnaire Universel</i ></p > ! end ! test Italics and ! wikitext foo ! html< p >< i > foo</i ></p > !end ! test Italics and ! wikitext foo ! html< p >< i > foo</i ></p > !end ! test Italics and ! wikitext foo ! html< p >< i > foo</i ></p > !end ! test Italics and ! wikitext foo ! html php< p >< i > foo</i ></p > ! html parsoid< p >< i > foo</i >< b ></b ></p > !end ! test Italics and ! wikitext foo ! html< p >< i > foo</i ></p > !end ! test Italics and ! wikitext foo ! html< p >< b > foo</b ></p > !end ! test Italics and ! wikitext foo ! html< p >< b > foo</b ></p > !end ! test Italics and ! wikitext foo ! html php< p >< b > foo</b ></p > ! html parsoid< p >< b > foo</b >< i ></i ></p > !end ! test Italics and ! wikitext foo ! html< p >< i > foo</i ></p > !end ! test Italics and ! wikitext foo ! html< p >< b > foo</b ></p > !end ! test Italics and ! wikitext foo ! html< p >< b > foo</b ></p > !end ! test Italics and ! wikitext foo ! html php< p >< b > foo</b ></p > ! html parsoid< p >< b > foo</b >< i ></i ></p > !end ! test Italics and ! options ! wikitext foo ! html< p >< b >< i > foo</i ></b ></p > !end ! test Italics and ! wikitext foo ! html< p >< i >< b > foo</b ></i ></p > !end ! test Italics and ! wikitext foo ! html< p >< i >< b > foo</b ></i ></p > !end ! test Italics and ! wikitext foo ! html< p >< i >< b > foo</b ></i ></p > !end ! test Italics and ! wikitext foo bar ! html< p >< i > foo< b > bar</b ></i ></p > !end ! test Italics and ! wikitext foo bar ! html< p >< i > foo< b > bar</b ></i ></p > !end ! test Italics and ! wikitext foo bar ! html< p >< i > foo< b > bar</b ></i ></p > !end ! test Italics and ! wikitext foo bar ! html php< p >< b > foo</b > bar</p > ! html parsoid< p >< b > foo</b > bar< i ></i ></p > !end ! test Italics and ! wikitext foo bar ! html php< p >< b > foo</b > bar</p > ! html parsoid< p >< b > foo</b > bar< b ></b ></p > !end ! test Italics and ! wikitext this is about foo s family ! html< p >< i > this is about< b > foo s family</b ></i ></p > !end ! test Italics and ! wikitext this is about foo s family ! html< p >< i > this is about< b > foo s</b > family</i ></p > !end ! test Italics and ! wikitext this is about foo s family ! html< p >< b > this is about< i > foo</i ></b >< i > s family</i ></p > !end ! test Italics and ! options ! wikitext this is about foo s family ! html< p >< i > this is about</i > foo< b > s family</b ></p > !end ! test Italics and ! wikitext this is about foo s family ! html< p >< b > this is about< i > foo s</i > family</b ></p > !end ! test Italicized possessive ! wikitext The s talk page ! html< p > The< i >< a href="/wiki/Main_Page" title="Main Page"> Main Page</a ></i > s talk page</p > ! end ! test Parsoid only
Definition: parserTests.txt:396
SFH_NO_HASH
const SFH_NO_HASH
Definition: Defines.php:240
$file
if(PHP_SAPI !='cli') $file
Definition: UtfNormalTest2.php:30
$wgArticlePath
$wgArticlePath
Definition: img_auth.php:48
$rev
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition: hooks.txt:1337
it
=Architecture==Two class hierarchies are used to provide the functionality associated with the different content models:*Content interface(and AbstractContent base class) define functionality that acts on the concrete content of a page, and *ContentHandler base class provides functionality specific to a content model, but not acting on concrete content. The most important function of ContentHandler is to act as a factory for the appropriate implementation of Content. These Content objects are to be used by MediaWiki everywhere, instead of passing page content around as text. All manipulation and analysis of page content must be done via the appropriate methods of the Content object. For each content model, a subclass of ContentHandler has to be registered with $wgContentHandlers. The ContentHandler object for a given content model can be obtained using ContentHandler::getForModelID($id). Also Title, WikiPage and Revision now have getContentHandler() methods for convenience. ContentHandler objects are singletons that provide functionality specific to the content type, but not directly acting on the content of some page. ContentHandler::makeEmptyContent() and ContentHandler::unserializeContent() can be used to create a Content object of the appropriate type. However, it is recommended to instead use WikiPage::getContent() resp. Revision::getContent() to get a page 's content as a Content object. These two methods should be the ONLY way in which page content is accessed. Another important function of ContentHandler objects is to define custom action handlers for a content model, see ContentHandler::getActionOverrides(). This is similar to what WikiPage::getActionOverrides() was already doing.==Serialization==With the ContentHandler facility, page content no longer has to be text based. Objects implementing the Content interface are used to represent and handle the content internally. For storage and data exchange, each content model supports at least one serialization format via ContentHandler::serializeContent($content). The list of supported formats for a given content model can be accessed using ContentHandler::getSupportedFormats(). Content serialization formats are identified using MIME type like strings. The following formats are built in:*text/x-wiki - wikitext *text/javascript - for js pages *text/css - for css pages *text/plain - for future use, e.g. with plain text messages. *text/html - for future use, e.g. with plain html messages. *application/vnd.php.serialized - for future use with the api and for extensions *application/json - for future use with the api, and for use by extensions *application/xml - for future use with the api, and for use by extensions In PHP, use the corresponding CONTENT_FORMAT_XXX constant. Note that when using the API to access page content, especially action=edit, action=parse and action=query &prop=revisions, the model and format of the content should always be handled explicitly. Without that information, interpretation of the provided content is not reliable. The same applies to XML dumps generated via maintenance/dumpBackup.php or Special:Export. Also note that the API will provide encapsulated, serialized content - so if the API was called with format=json, and contentformat is also json(or rather, application/json), the page content is represented as a string containing an escaped json structure. Extensions that use JSON to serialize some types of page content may provide specialized API modules that allow access to that content in a more natural form.==Compatibility==The ContentHandler facility is introduced in a way that should allow all existing code to keep functioning at least for pages that contain wikitext or other text based content. However, a number of functions and hooks have been deprecated in favor of new versions that are aware of the page 's content model, and will now generate warnings when used. Most importantly, the following functions have been deprecated:*Revisions::getText() and Revisions::getRawText() is deprecated in favor Revisions::getContent() *WikiPage::getText() is deprecated in favor WikiPage::getContent() Also, the old Article::getContent()(which returns text) is superceded by Article::getContentObject(). However, both methods should be avoided since they do not provide clean access to the page 's actual content. For instance, they may return a system message for non-existing pages. Use WikiPage::getContent() instead. Code that relies on a textual representation of the page content should eventually be rewritten. However, ContentHandler::getContentText() provides a stop-gap that can be used to get text for a page. Its behavior is controlled by $wgContentHandlerTextFallback it
Definition: contenthandler.txt:107
broken
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped broken
Definition: hooks.txt:1530
$args
if( $line===false) $args
Definition: cdb.php:62
OT_HTML
const OT_HTML
Definition: Defines.php:230
DB_SLAVE
const DB_SLAVE
Definition: Defines.php:55
Title
Represents a title within MediaWiki.
Definition: Title.php:35
CoreParserFunctions\cascadingsources
static cascadingsources( $parser, $title='')
Returns the sources of any cascading protection acting on a specified page.
Definition: CoreParserFunctions.php:1170
like
For a write use something like
Definition: database.txt:26
type
This document describes the state of Postgres support in and is fairly well maintained The main code is very well while extensions are very hit and miss it is probably the most supported database after MySQL Much of the work in making MediaWiki database agnostic came about through the work of creating Postgres as and are nearing end of but without copying over all the usage comments General notes on the but these can almost always be programmed around *Although Postgres has a true BOOLEAN type
Definition: postgres.txt:22
MagicWord\getSubstIDs
static getSubstIDs()
Get an array of parser substitution modifier IDs.
Definition: MagicWord.php:265
wfMatchesDomainList
wfMatchesDomainList( $url, $domains)
Check whether a given URL has a domain that occurs in a given set of domains.
Definition: GlobalFunctions.php:949
Sanitizer\fixTagAttributes
static fixTagAttributes( $text, $element)
Take a tag soup fragment listing an HTML element's attributes and normalize it to well-formed XML,...
Definition: Sanitizer.php:1005
output
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling output() to send it all. It could be easily changed to send incrementally if that becomes useful
Xml\isWellFormedXmlFragment
static isWellFormedXmlFragment( $text)
Check if a string is a well-formed XML fragment.
Definition: Xml.php:718
$term
the value to return A Title object or null whereas SearchGetNearMatch runs after $term
Definition: hooks.txt:2136
in
Prior to maintenance scripts were a hodgepodge of code that had no cohesion or formal method of action Beginning in
Definition: maintenance.txt:1
on
We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going on
Definition: hooks.txt:86
things
magicword txt Magic Words are some phrases used in the wikitext They are used for two things
Definition: magicword.txt:4
TS_UNIX
const TS_UNIX
Unix time - the number of seconds since 1970-01-01 00:00:00 UTC.
Definition: GlobalFunctions.php:2473
used
you don t have to do a grep find to see where the $wgReverseTitle variable is used
Definition: hooks.txt:117
$output
& $output
Definition: hooks.txt:375
format
if the prop value should be in the metadata multi language array format
Definition: hooks.txt:1230
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
Linker\tocUnindent
static tocUnindent( $level)
Finish one or more sublevels on the Table of Contents.
Definition: Linker.php:1633
wfFindFile
wfFindFile( $title, $options=array())
Find a file.
Definition: GlobalFunctions.php:3757
Linker\makeMediaLinkFile
static makeMediaLinkFile(Title $title, $file, $html='')
Create a direct link to a given uploaded file.
Definition: Linker.php:993
StringUtils\delimiterReplace
static delimiterReplace( $startDelim, $endDelim, $replace, $subject, $flags='')
Perform an operation equivalent to.
Definition: StringUtils.php:256
Linker\makeImageLink
static makeImageLink($parser, Title $title, $file, $frameParams=array(), $handlerParams=array(), $time=false, $query="", $widthOption=null)
Given parameters derived from [[Image:Foo|options...]], generate the HTML that that syntax inserts in...
Definition: Linker.php:539
ParserLimitReportPrepare
namespace are movable Hooks may change this value to override the return value of MWNamespace::isMovable(). 'NewRevisionFromEditComplete' if it s text intended for display in a monospaced font $report should be output in English ParserLimitReportPrepare
Definition: hooks.txt:1751
MagicWord\getDoubleUnderscoreArray
static getDoubleUnderscoreArray()
Get a MagicWordArray of double-underscore entities.
Definition: MagicWord.php:288
Sanitizer\normalizeCharReferences
static normalizeCharReferences( $text)
Ensure that any entities and character references are legal for XML and XHTML specifically.
Definition: Sanitizer.php:1317
Linker\normalizeSubpageLink
static normalizeSubpageLink( $contextTitle, $target, &$text)
Definition: Linker.php:1483
SiteStats\views
static views()
Definition: SiteStats.php:108
Sanitizer\decodeTagAttributes
static decodeTagAttributes( $text)
Return an associative array of attribute names and values from a partial tag string.
Definition: Sanitizer.php:1184
from
Please log in again after you receive it</td >< td > s a saved copy from
Definition: All_system_messages.txt:3297
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:87
that
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global then executing the whole list after the page is displayed We don t do anything smart like collating updates to the same table or such because the list is almost always going to have just one item on if that
Definition: deferred.txt:11
$t
$t
Definition: testCompression.php:65
Title\legalChars
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:529
Sanitizer\decodeCharReferences
static decodeCharReferences( $text)
Decode any character references, numeric or named entities, in the text and return a UTF-8 string.
Definition: Sanitizer.php:1414
$error
usually copyright or history_copyright This message must be in HTML not wikitext $subpages will be ignored and the rest of subPageSubtitle() will run. 'SkinTemplateBuildNavUrlsNav_urlsAfterPermalink' whether MediaWiki currently thinks this is a CSS JS page Hooks may change this value to override the return value of Title::isCssOrJsPage(). 'TitleIsAlwaysKnown' whether MediaWiki currently thinks this page is known isMovable() always returns false. $title whether MediaWiki currently thinks this page is movable Hooks may change this value to override the return value of Title::isMovable(). 'TitleIsWikitextPage' whether MediaWiki currently thinks this is a wikitext page Hooks may change this value to override the return value of Title::isWikitextPage() 'TitleMove' use UploadVerification and UploadVerifyFile instead where the first element is the message key and the remaining elements are used as parameters to the message based on mime etc Preferred in most cases over UploadVerification object with all info about the upload string as detected by MediaWiki Handlers will typically only apply for specific mime types object & $error
Definition: hooks.txt:2584
$query
return true to allow those checks to and false if checking is done use this to change the tables headers temp or archived zone change it to an object instance and return false override the list derivative used the name of the old file when set the default code will be skipped add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1105
$attribs
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Definition: hooks.txt:1530
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:59
$res
$res
Definition: database.txt:21
LinkCache\singleton
static & singleton()
Get an instance of this class.
Definition: LinkCache.php:49
MWTimestamp\getLocalInstance
static getLocalInstance( $ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
Definition: MWTimestamp.php:373
wfGetCaller
wfGetCaller( $level=2)
Get the name of the function which called this function wfGetCaller( 1 ) is the function with the wfG...
Definition: GlobalFunctions.php:1988
MWHttpRequest\factory
static factory( $url, $options=null)
Generate a new request object.
Definition: HttpFunctions.php:289
Linker\makeExternalImage
static makeExternalImage( $url, $alt='')
Return the code for images which were added via external links, via Parser::maybeMakeExternalImage().
Definition: Linker.php:487
lt
div & lt
Definition: hooks.txt:1631
SiteStats\edits
static edits()
Definition: SiteStats.php:116
if
if(!function_exists('version_compare')||version_compare(phpversion(), '5.3.2')< 0)
Definition: api.php:37
line
I won t presume to tell you how to I m just describing the methods I chose to use for myself If you do choose to follow these it will probably be easier for you to collaborate with others on the but if you want to contribute without by all means do which work well I also use K &R brace matching style I know that s a religious issue for so if you want to use a style that puts opening braces on the next line
Definition: design.txt:79
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:1632
SpecialVersion\getVersion
static getVersion( $flags='')
Return a string of the MediaWiki version with SVN revision if available.
Definition: SpecialVersion.php:246
$wgTitle
if(! $wgRequest->checkUrlExtension()) if(! $wgEnableAPI) $wgTitle
Definition: api.php:63
page
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values my talk page
Definition: hooks.txt:1961
wfRandomString
wfRandomString( $length=32)
Get a random string containing a number of pseudo-random hex characters.
Definition: GlobalFunctions.php:347
Sanitizer\removeHTMLtags
static removeHTMLtags( $text, $processCallback=null, $args=array(), $extratags=array(), $removetags=array())
Cleans up HTML, removes dangerous tags and attributes, and removes HTML comments.
Definition: Sanitizer.php:366
$type
$type
Definition: testCompression.php:46
MWTidy\tidy
static tidy( $text)
Interface with html tidy, used if $wgUseTidy = true.
Definition: Tidy.php:126