MediaWiki  1.23.15
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  # Replace unnecessary URL escape codes with their equivalent characters
1378  $pasteurized = self::replaceUnusualEscapes( $url );
1379  $this->mOutput->addExternalLink( $pasteurized );
1380  }
1381  wfProfileOut( __METHOD__ );
1382  return $text . $trail;
1383  }
1384 
1394  function doHeadings( $text ) {
1395  wfProfileIn( __METHOD__ );
1396  for ( $i = 6; $i >= 1; --$i ) {
1397  $h = str_repeat( '=', $i );
1398  $text = preg_replace( "/^$h(.+)$h\\s*$/m", "<h$i>\\1</h$i>", $text );
1399  }
1400  wfProfileOut( __METHOD__ );
1401  return $text;
1402  }
1403 
1412  function doAllQuotes( $text ) {
1413  wfProfileIn( __METHOD__ );
1414  $outtext = '';
1415  $lines = StringUtils::explode( "\n", $text );
1416  foreach ( $lines as $line ) {
1417  $outtext .= $this->doQuotes( $line ) . "\n";
1418  }
1419  $outtext = substr( $outtext, 0, -1 );
1420  wfProfileOut( __METHOD__ );
1421  return $outtext;
1422  }
1423 
1431  public function doQuotes( $text ) {
1432  $arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1433  $countarr = count( $arr );
1434  if ( $countarr == 1 ) {
1435  return $text;
1436  }
1437 
1438  // First, do some preliminary work. This may shift some apostrophes from
1439  // being mark-up to being text. It also counts the number of occurrences
1440  // of bold and italics mark-ups.
1441  $numbold = 0;
1442  $numitalics = 0;
1443  for ( $i = 1; $i < $countarr; $i += 2 ) {
1444  $thislen = strlen( $arr[$i] );
1445  // If there are ever four apostrophes, assume the first is supposed to
1446  // be text, and the remaining three constitute mark-up for bold text.
1447  // (bug 13227: ''''foo'''' turns into ' ''' foo ' ''')
1448  if ( $thislen == 4 ) {
1449  $arr[$i - 1] .= "'";
1450  $arr[$i] = "'''";
1451  $thislen = 3;
1452  } elseif ( $thislen > 5 ) {
1453  // If there are more than 5 apostrophes in a row, assume they're all
1454  // text except for the last 5.
1455  // (bug 13227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
1456  $arr[$i - 1] .= str_repeat( "'", $thislen - 5 );
1457  $arr[$i] = "'''''";
1458  $thislen = 5;
1459  }
1460  // Count the number of occurrences of bold and italics mark-ups.
1461  if ( $thislen == 2 ) {
1462  $numitalics++;
1463  } elseif ( $thislen == 3 ) {
1464  $numbold++;
1465  } elseif ( $thislen == 5 ) {
1466  $numitalics++;
1467  $numbold++;
1468  }
1469  }
1470 
1471  // If there is an odd number of both bold and italics, it is likely
1472  // that one of the bold ones was meant to be an apostrophe followed
1473  // by italics. Which one we cannot know for certain, but it is more
1474  // likely to be one that has a single-letter word before it.
1475  if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
1476  $firstsingleletterword = -1;
1477  $firstmultiletterword = -1;
1478  $firstspace = -1;
1479  for ( $i = 1; $i < $countarr; $i += 2 ) {
1480  if ( strlen( $arr[$i] ) == 3 ) {
1481  $x1 = substr( $arr[$i - 1], -1 );
1482  $x2 = substr( $arr[$i - 1], -2, 1 );
1483  if ( $x1 === ' ' ) {
1484  if ( $firstspace == -1 ) {
1485  $firstspace = $i;
1486  }
1487  } elseif ( $x2 === ' ' ) {
1488  if ( $firstsingleletterword == -1 ) {
1489  $firstsingleletterword = $i;
1490  // if $firstsingleletterword is set, we don't
1491  // look at the other options, so we can bail early.
1492  break;
1493  }
1494  } else {
1495  if ( $firstmultiletterword == -1 ) {
1496  $firstmultiletterword = $i;
1497  }
1498  }
1499  }
1500  }
1501 
1502  // If there is a single-letter word, use it!
1503  if ( $firstsingleletterword > -1 ) {
1504  $arr[$firstsingleletterword] = "''";
1505  $arr[$firstsingleletterword - 1] .= "'";
1506  } elseif ( $firstmultiletterword > -1 ) {
1507  // If not, but there's a multi-letter word, use that one.
1508  $arr[$firstmultiletterword] = "''";
1509  $arr[$firstmultiletterword - 1] .= "'";
1510  } elseif ( $firstspace > -1 ) {
1511  // ... otherwise use the first one that has neither.
1512  // (notice that it is possible for all three to be -1 if, for example,
1513  // there is only one pentuple-apostrophe in the line)
1514  $arr[$firstspace] = "''";
1515  $arr[$firstspace - 1] .= "'";
1516  }
1517  }
1518 
1519  // Now let's actually convert our apostrophic mush to HTML!
1520  $output = '';
1521  $buffer = '';
1522  $state = '';
1523  $i = 0;
1524  foreach ( $arr as $r ) {
1525  if ( ( $i % 2 ) == 0 ) {
1526  if ( $state === 'both' ) {
1527  $buffer .= $r;
1528  } else {
1529  $output .= $r;
1530  }
1531  } else {
1532  $thislen = strlen( $r );
1533  if ( $thislen == 2 ) {
1534  if ( $state === 'i' ) {
1535  $output .= '</i>';
1536  $state = '';
1537  } elseif ( $state === 'bi' ) {
1538  $output .= '</i>';
1539  $state = 'b';
1540  } elseif ( $state === 'ib' ) {
1541  $output .= '</b></i><b>';
1542  $state = 'b';
1543  } elseif ( $state === 'both' ) {
1544  $output .= '<b><i>' . $buffer . '</i>';
1545  $state = 'b';
1546  } else { // $state can be 'b' or ''
1547  $output .= '<i>';
1548  $state .= 'i';
1549  }
1550  } elseif ( $thislen == 3 ) {
1551  if ( $state === 'b' ) {
1552  $output .= '</b>';
1553  $state = '';
1554  } elseif ( $state === 'bi' ) {
1555  $output .= '</i></b><i>';
1556  $state = 'i';
1557  } elseif ( $state === 'ib' ) {
1558  $output .= '</b>';
1559  $state = 'i';
1560  } elseif ( $state === 'both' ) {
1561  $output .= '<i><b>' . $buffer . '</b>';
1562  $state = 'i';
1563  } else { // $state can be 'i' or ''
1564  $output .= '<b>';
1565  $state .= 'b';
1566  }
1567  } elseif ( $thislen == 5 ) {
1568  if ( $state === 'b' ) {
1569  $output .= '</b><i>';
1570  $state = 'i';
1571  } elseif ( $state === 'i' ) {
1572  $output .= '</i><b>';
1573  $state = 'b';
1574  } elseif ( $state === 'bi' ) {
1575  $output .= '</i></b>';
1576  $state = '';
1577  } elseif ( $state === 'ib' ) {
1578  $output .= '</b></i>';
1579  $state = '';
1580  } elseif ( $state === 'both' ) {
1581  $output .= '<i><b>' . $buffer . '</b></i>';
1582  $state = '';
1583  } else { // ($state == '')
1584  $buffer = '';
1585  $state = 'both';
1586  }
1587  }
1588  }
1589  $i++;
1590  }
1591  // Now close all remaining tags. Notice that the order is important.
1592  if ( $state === 'b' || $state === 'ib' ) {
1593  $output .= '</b>';
1594  }
1595  if ( $state === 'i' || $state === 'bi' || $state === 'ib' ) {
1596  $output .= '</i>';
1597  }
1598  if ( $state === 'bi' ) {
1599  $output .= '</b>';
1600  }
1601  // There might be lonely ''''', so make sure we have a buffer
1602  if ( $state === 'both' && $buffer ) {
1603  $output .= '<b><i>' . $buffer . '</i></b>';
1604  }
1605  return $output;
1606  }
1607 
1621  function replaceExternalLinks( $text ) {
1622  wfProfileIn( __METHOD__ );
1623 
1624  $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1625  if ( $bits === false ) {
1626  wfProfileOut( __METHOD__ );
1627  throw new MWException( "PCRE needs to be compiled with --enable-unicode-properties in order for MediaWiki to function" );
1628  }
1629  $s = array_shift( $bits );
1630 
1631  $i = 0;
1632  while ( $i < count( $bits ) ) {
1633  $url = $bits[$i++];
1634  $i++; // protocol
1635  $text = $bits[$i++];
1636  $trail = $bits[$i++];
1637 
1638  # The characters '<' and '>' (which were escaped by
1639  # removeHTMLtags()) should not be included in
1640  # URLs, per RFC 2396.
1641  $m2 = array();
1642  if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
1643  $text = substr( $url, $m2[0][1] ) . ' ' . $text;
1644  $url = substr( $url, 0, $m2[0][1] );
1645  }
1646 
1647  # If the link text is an image URL, replace it with an <img> tag
1648  # This happened by accident in the original parser, but some people used it extensively
1649  $img = $this->maybeMakeExternalImage( $text );
1650  if ( $img !== false ) {
1651  $text = $img;
1652  }
1653 
1654  $dtrail = '';
1655 
1656  # Set linktype for CSS - if URL==text, link is essentially free
1657  $linktype = ( $text === $url ) ? 'free' : 'text';
1658 
1659  # No link text, e.g. [http://domain.tld/some.link]
1660  if ( $text == '' ) {
1661  # Autonumber
1662  $langObj = $this->getTargetLanguage();
1663  $text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
1664  $linktype = 'autonumber';
1665  } else {
1666  # Have link text, e.g. [http://domain.tld/some.link text]s
1667  # Check for trail
1668  list( $dtrail, $trail ) = Linker::splitTrail( $trail );
1669  }
1670 
1671  $text = $this->getConverterLanguage()->markNoConversion( $text );
1672 
1673  $url = Sanitizer::cleanUrl( $url );
1674 
1675  # Use the encoded URL
1676  # This means that users can paste URLs directly into the text
1677  # Funny characters like ö aren't valid in URLs anyway
1678  # This was changed in August 2004
1679  $s .= Linker::makeExternalLink( $url, $text, false, $linktype,
1680  $this->getExternalLinkAttribs( $url ) ) . $dtrail . $trail;
1681 
1682  # Register link in the output object.
1683  # Replace unnecessary URL escape codes with the referenced character
1684  # This prevents spammers from hiding links from the filters
1685  $pasteurized = self::replaceUnusualEscapes( $url );
1686  $this->mOutput->addExternalLink( $pasteurized );
1687  }
1688 
1689  wfProfileOut( __METHOD__ );
1690  return $s;
1691  }
1692 
1702  public static function getExternalLinkRel( $url = false, $title = null ) {
1703  global $wgNoFollowLinks, $wgNoFollowNsExceptions, $wgNoFollowDomainExceptions;
1704  $ns = $title ? $title->getNamespace() : false;
1705  if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions )
1706  && !wfMatchesDomainList( $url, $wgNoFollowDomainExceptions )
1707  ) {
1708  return 'nofollow';
1709  }
1710  return null;
1711  }
1712 
1723  function getExternalLinkAttribs( $url = false ) {
1724  $attribs = array();
1725  $rel = self::getExternalLinkRel( $url, $this->mTitle );
1726 
1727  $target = $this->mOptions->getExternalLinkTarget();
1728  if ( $target ) {
1729  $attribs['target'] = $target;
1730  if ( !in_array( $target, array( '_self', '_parent', '_top' ) ) ) {
1731  // T133507. New windows can navigate parent cross-origin.
1732  // Including noreferrer due to lacking browser
1733  // support of noopener. Eventually noreferrer should be removed.
1734  if ( $rel !== '' ) {
1735  $rel .= ' ';
1736  }
1737  $rel .= 'noreferrer noopener';
1738  }
1739  }
1740  $attribs['rel'] = $rel;
1741  return $attribs;
1742  }
1743 
1755  static function replaceUnusualEscapes( $url ) {
1756  return preg_replace_callback( '/%[0-9A-Fa-f]{2}/',
1757  array( __CLASS__, 'replaceUnusualEscapesCallback' ), $url );
1758  }
1759 
1768  private static function replaceUnusualEscapesCallback( $matches ) {
1769  $char = urldecode( $matches[0] );
1770  $ord = ord( $char );
1771  # Is it an unsafe or HTTP reserved character according to RFC 1738?
1772  if ( $ord > 32 && $ord < 127 && strpos( '<>"#{}|\^~[]`;/?', $char ) === false ) {
1773  # No, shouldn't be escaped
1774  return $char;
1775  } else {
1776  # Yes, leave it escaped
1777  return $matches[0];
1778  }
1779  }
1780 
1790  function maybeMakeExternalImage( $url ) {
1791  $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
1792  $imagesexception = !empty( $imagesfrom );
1793  $text = false;
1794  # $imagesfrom could be either a single string or an array of strings, parse out the latter
1795  if ( $imagesexception && is_array( $imagesfrom ) ) {
1796  $imagematch = false;
1797  foreach ( $imagesfrom as $match ) {
1798  if ( strpos( $url, $match ) === 0 ) {
1799  $imagematch = true;
1800  break;
1801  }
1802  }
1803  } elseif ( $imagesexception ) {
1804  $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
1805  } else {
1806  $imagematch = false;
1807  }
1808  if ( $this->mOptions->getAllowExternalImages()
1809  || ( $imagesexception && $imagematch ) ) {
1810  if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
1811  # Image found
1812  $text = Linker::makeExternalImage( $url );
1813  }
1814  }
1815  if ( !$text && $this->mOptions->getEnableImageWhitelist()
1816  && preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
1817  $whitelist = explode( "\n", wfMessage( 'external_image_whitelist' )->inContentLanguage()->text() );
1818  foreach ( $whitelist as $entry ) {
1819  # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
1820  if ( strpos( $entry, '#' ) === 0 || $entry === '' ) {
1821  continue;
1822  }
1823  if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
1824  # Image matches a whitelist entry
1825  $text = Linker::makeExternalImage( $url );
1826  break;
1827  }
1828  }
1829  }
1830  return $text;
1831  }
1832 
1842  function replaceInternalLinks( $s ) {
1843  $this->mLinkHolders->merge( $this->replaceInternalLinks2( $s ) );
1844  return $s;
1845  }
1846 
1855  function replaceInternalLinks2( &$s ) {
1856  wfProfileIn( __METHOD__ );
1857 
1858  wfProfileIn( __METHOD__ . '-setup' );
1859  static $tc = false, $e1, $e1_img;
1860  # the % is needed to support urlencoded titles as well
1861  if ( !$tc ) {
1862  $tc = Title::legalChars() . '#%';
1863  # Match a link having the form [[namespace:link|alternate]]trail
1864  $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
1865  # Match cases where there is no "]]", which might still be images
1866  $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
1867  }
1868 
1869  $holders = new LinkHolderArray( $this );
1870 
1871  # split the entire text string on occurrences of [[
1872  $a = StringUtils::explode( '[[', ' ' . $s );
1873  # get the first element (all text up to first [[), and remove the space we added
1874  $s = $a->current();
1875  $a->next();
1876  $line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
1877  $s = substr( $s, 1 );
1878 
1879  $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
1880  $e2 = null;
1881  if ( $useLinkPrefixExtension ) {
1882  # Match the end of a line for a word that's not followed by whitespace,
1883  # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
1885  $charset = $wgContLang->linkPrefixCharset();
1886  $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
1887  }
1888 
1889  if ( is_null( $this->mTitle ) ) {
1890  wfProfileOut( __METHOD__ . '-setup' );
1891  wfProfileOut( __METHOD__ );
1892  throw new MWException( __METHOD__ . ": \$this->mTitle is null\n" );
1893  }
1894  $nottalk = !$this->mTitle->isTalkPage();
1895 
1896  if ( $useLinkPrefixExtension ) {
1897  $m = array();
1898  if ( preg_match( $e2, $s, $m ) ) {
1899  $first_prefix = $m[2];
1900  } else {
1901  $first_prefix = false;
1902  }
1903  } else {
1904  $prefix = '';
1905  }
1906 
1907  $useSubpages = $this->areSubpagesAllowed();
1908  wfProfileOut( __METHOD__ . '-setup' );
1909 
1910  # Loop for each link
1911  for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) {
1912  # Check for excessive memory usage
1913  if ( $holders->isBig() ) {
1914  # Too big
1915  # Do the existence check, replace the link holders and clear the array
1916  $holders->replace( $s );
1917  $holders->clear();
1918  }
1919 
1920  if ( $useLinkPrefixExtension ) {
1921  wfProfileIn( __METHOD__ . '-prefixhandling' );
1922  if ( preg_match( $e2, $s, $m ) ) {
1923  $prefix = $m[2];
1924  $s = $m[1];
1925  } else {
1926  $prefix = '';
1927  }
1928  # first link
1929  if ( $first_prefix ) {
1930  $prefix = $first_prefix;
1931  $first_prefix = false;
1932  }
1933  wfProfileOut( __METHOD__ . '-prefixhandling' );
1934  }
1935 
1936  $might_be_img = false;
1937 
1938  wfProfileIn( __METHOD__ . "-e1" );
1939  if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
1940  $text = $m[2];
1941  # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
1942  # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
1943  # the real problem is with the $e1 regex
1944  # See bug 1300.
1945  #
1946  # Still some problems for cases where the ] is meant to be outside punctuation,
1947  # and no image is in sight. See bug 2095.
1948  #
1949  if ( $text !== ''
1950  && substr( $m[3], 0, 1 ) === ']'
1951  && strpos( $text, '[' ) !== false
1952  ) {
1953  $text .= ']'; # so that replaceExternalLinks($text) works later
1954  $m[3] = substr( $m[3], 1 );
1955  }
1956  # fix up urlencoded title texts
1957  if ( strpos( $m[1], '%' ) !== false ) {
1958  # Should anchors '#' also be rejected?
1959  $m[1] = str_replace( array( '<', '>' ), array( '&lt;', '&gt;' ), rawurldecode( $m[1] ) );
1960  }
1961  $trail = $m[3];
1962  } elseif ( preg_match( $e1_img, $line, $m ) ) { # Invalid, but might be an image with a link in its caption
1963  $might_be_img = true;
1964  $text = $m[2];
1965  if ( strpos( $m[1], '%' ) !== false ) {
1966  $m[1] = str_replace( array( '<', '>' ), array( '&lt;', '&gt;' ), rawurldecode( $m[1] ) );
1967  }
1968  $trail = "";
1969  } else { # Invalid form; output directly
1970  $s .= $prefix . '[[' . $line;
1971  wfProfileOut( __METHOD__ . "-e1" );
1972  continue;
1973  }
1974  wfProfileOut( __METHOD__ . "-e1" );
1975  wfProfileIn( __METHOD__ . "-misc" );
1976 
1977  # Don't allow internal links to pages containing
1978  # PROTO: where PROTO is a valid URL protocol; these
1979  # should be external links.
1980  if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $m[1] ) ) {
1981  $s .= $prefix . '[[' . $line;
1982  wfProfileOut( __METHOD__ . "-misc" );
1983  continue;
1984  }
1985 
1986  # Make subpage if necessary
1987  if ( $useSubpages ) {
1988  $link = $this->maybeDoSubpageLink( $m[1], $text );
1989  } else {
1990  $link = $m[1];
1991  }
1992 
1993  $noforce = ( substr( $m[1], 0, 1 ) !== ':' );
1994  if ( !$noforce ) {
1995  # Strip off leading ':'
1996  $link = substr( $link, 1 );
1997  }
1998 
1999  wfProfileOut( __METHOD__ . "-misc" );
2000  wfProfileIn( __METHOD__ . "-title" );
2001  $nt = Title::newFromText( $this->mStripState->unstripNoWiki( $link ) );
2002  if ( $nt === null ) {
2003  $s .= $prefix . '[[' . $line;
2004  wfProfileOut( __METHOD__ . "-title" );
2005  continue;
2006  }
2007 
2008  $ns = $nt->getNamespace();
2009  $iw = $nt->getInterwiki();
2010  wfProfileOut( __METHOD__ . "-title" );
2011 
2012  if ( $might_be_img ) { # if this is actually an invalid link
2013  wfProfileIn( __METHOD__ . "-might_be_img" );
2014  if ( $ns == NS_FILE && $noforce ) { # but might be an image
2015  $found = false;
2016  while ( true ) {
2017  # look at the next 'line' to see if we can close it there
2018  $a->next();
2019  $next_line = $a->current();
2020  if ( $next_line === false || $next_line === null ) {
2021  break;
2022  }
2023  $m = explode( ']]', $next_line, 3 );
2024  if ( count( $m ) == 3 ) {
2025  # the first ]] closes the inner link, the second the image
2026  $found = true;
2027  $text .= "[[{$m[0]}]]{$m[1]}";
2028  $trail = $m[2];
2029  break;
2030  } elseif ( count( $m ) == 2 ) {
2031  # if there's exactly one ]] that's fine, we'll keep looking
2032  $text .= "[[{$m[0]}]]{$m[1]}";
2033  } else {
2034  # if $next_line is invalid too, we need look no further
2035  $text .= '[[' . $next_line;
2036  break;
2037  }
2038  }
2039  if ( !$found ) {
2040  # we couldn't find the end of this imageLink, so output it raw
2041  # but don't ignore what might be perfectly normal links in the text we've examined
2042  $holders->merge( $this->replaceInternalLinks2( $text ) );
2043  $s .= "{$prefix}[[$link|$text";
2044  # note: no $trail, because without an end, there *is* no trail
2045  wfProfileOut( __METHOD__ . "-might_be_img" );
2046  continue;
2047  }
2048  } else { # it's not an image, so output it raw
2049  $s .= "{$prefix}[[$link|$text";
2050  # note: no $trail, because without an end, there *is* no trail
2051  wfProfileOut( __METHOD__ . "-might_be_img" );
2052  continue;
2053  }
2054  wfProfileOut( __METHOD__ . "-might_be_img" );
2055  }
2056 
2057  $wasblank = ( $text == '' );
2058  if ( $wasblank ) {
2059  $text = $link;
2060  } else {
2061  # Bug 4598 madness. Handle the quotes only if they come from the alternate part
2062  # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2063  # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2064  # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2065  $text = $this->doQuotes( $text );
2066  }
2067 
2068  # Link not escaped by : , create the various objects
2069  if ( $noforce ) {
2070  # Interwikis
2071  wfProfileIn( __METHOD__ . "-interwiki" );
2072  if ( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && Language::fetchLanguageName( $iw, null, 'mw' ) ) {
2073  // XXX: the above check prevents links to sites with identifiers that are not language codes
2074 
2075  # Bug 24502: filter duplicates
2076  if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
2077  $this->mLangLinkLanguages[$iw] = true;
2078  $this->mOutput->addLanguageLink( $nt->getFullText() );
2079  }
2080 
2081  $s = rtrim( $s . $prefix );
2082  $s .= trim( $trail, "\n" ) == '' ? '': $prefix . $trail;
2083  wfProfileOut( __METHOD__ . "-interwiki" );
2084  continue;
2085  }
2086  wfProfileOut( __METHOD__ . "-interwiki" );
2087 
2088  if ( $ns == NS_FILE ) {
2089  wfProfileIn( __METHOD__ . "-image" );
2090  if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
2091  if ( $wasblank ) {
2092  # if no parameters were passed, $text
2093  # becomes something like "File:Foo.png",
2094  # which we don't want to pass on to the
2095  # image generator
2096  $text = '';
2097  } else {
2098  # recursively parse links inside the image caption
2099  # actually, this will parse them in any other parameters, too,
2100  # but it might be hard to fix that, and it doesn't matter ATM
2101  $text = $this->replaceExternalLinks( $text );
2102  $holders->merge( $this->replaceInternalLinks2( $text ) );
2103  }
2104  # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
2105  $s .= $prefix . $this->armorLinks(
2106  $this->makeImage( $nt, $text, $holders ) ) . $trail;
2107  } else {
2108  $s .= $prefix . $trail;
2109  }
2110  wfProfileOut( __METHOD__ . "-image" );
2111  continue;
2112  }
2113 
2114  if ( $ns == NS_CATEGORY ) {
2115  wfProfileIn( __METHOD__ . "-category" );
2116  $s = rtrim( $s . "\n" ); # bug 87
2117 
2118  if ( $wasblank ) {
2119  $sortkey = $this->getDefaultSort();
2120  } else {
2121  $sortkey = $text;
2122  }
2123  $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2124  $sortkey = str_replace( "\n", '', $sortkey );
2125  $sortkey = $this->getConverterLanguage()->convertCategoryKey( $sortkey );
2126  $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2127 
2131  $s .= trim( $prefix . $trail, "\n" ) == '' ? '' : $prefix . $trail;
2132 
2133  wfProfileOut( __METHOD__ . "-category" );
2134  continue;
2135  }
2136  }
2137 
2138  # Self-link checking. For some languages, variants of the title are checked in
2139  # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2140  # for linking to a different variant.
2141  if ( $ns != NS_SPECIAL && $nt->equals( $this->mTitle ) && !$nt->hasFragment() ) {
2142  $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail );
2143  continue;
2144  }
2145 
2146  # NS_MEDIA is a pseudo-namespace for linking directly to a file
2147  # @todo FIXME: Should do batch file existence checks, see comment below
2148  if ( $ns == NS_MEDIA ) {
2149  wfProfileIn( __METHOD__ . "-media" );
2150  # Give extensions a chance to select the file revision for us
2151  $options = array();
2152  $descQuery = false;
2153  wfRunHooks( 'BeforeParserFetchFileAndTitle',
2154  array( $this, $nt, &$options, &$descQuery ) );
2155  # Fetch and register the file (file title may be different via hooks)
2156  list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $options );
2157  # Cloak with NOPARSE to avoid replacement in replaceExternalLinks
2158  $s .= $prefix . $this->armorLinks(
2159  Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2160  wfProfileOut( __METHOD__ . "-media" );
2161  continue;
2162  }
2163 
2164  wfProfileIn( __METHOD__ . "-always_known" );
2165  # Some titles, such as valid special pages or files in foreign repos, should
2166  # be shown as bluelinks even though they're not included in the page table
2167  #
2168  # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2169  # batch file existence checks for NS_FILE and NS_MEDIA
2170  if ( $iw == '' && $nt->isAlwaysKnown() ) {
2171  $this->mOutput->addLink( $nt );
2172  $s .= $this->makeKnownLinkHolder( $nt, $text, array(), $trail, $prefix );
2173  } else {
2174  # Links will be added to the output link list after checking
2175  $s .= $holders->makeHolder( $nt, $text, array(), $trail, $prefix );
2176  }
2177  wfProfileOut( __METHOD__ . "-always_known" );
2178  }
2179  wfProfileOut( __METHOD__ );
2180  return $holders;
2181  }
2182 
2197  function makeKnownLinkHolder( $nt, $text = '', $query = array(), $trail = '', $prefix = '' ) {
2198  list( $inside, $trail ) = Linker::splitTrail( $trail );
2199 
2200  if ( is_string( $query ) ) {
2201  $query = wfCgiToArray( $query );
2202  }
2203  if ( $text == '' ) {
2204  $text = htmlspecialchars( $nt->getPrefixedText() );
2205  }
2206 
2207  $link = Linker::linkKnown( $nt, "$prefix$text$inside", array(), $query );
2208 
2209  return $this->armorLinks( $link ) . $trail;
2210  }
2211 
2222  function armorLinks( $text ) {
2223  return preg_replace( '/\b((?i)' . $this->mUrlProtocols . ')/',
2224  "{$this->mUniqPrefix}NOPARSE$1", $text );
2225  }
2226 
2231  function areSubpagesAllowed() {
2232  # Some namespaces don't allow subpages
2233  return MWNamespace::hasSubpages( $this->mTitle->getNamespace() );
2234  }
2235 
2244  function maybeDoSubpageLink( $target, &$text ) {
2245  return Linker::normalizeSubpageLink( $this->mTitle, $target, $text );
2246  }
2247 
2254  function closeParagraph() {
2255  $result = '';
2256  if ( $this->mLastSection != '' ) {
2257  $result = '</' . $this->mLastSection . ">\n";
2258  }
2259  $this->mInPre = false;
2260  $this->mLastSection = '';
2261  return $result;
2262  }
2263 
2274  function getCommon( $st1, $st2 ) {
2275  $fl = strlen( $st1 );
2276  $shorter = strlen( $st2 );
2277  if ( $fl < $shorter ) {
2278  $shorter = $fl;
2279  }
2280 
2281  for ( $i = 0; $i < $shorter; ++$i ) {
2282  if ( $st1[$i] != $st2[$i] ) {
2283  break;
2284  }
2285  }
2286  return $i;
2287  }
2288 
2298  function openList( $char ) {
2299  $result = $this->closeParagraph();
2300 
2301  if ( '*' === $char ) {
2302  $result .= "<ul>\n<li>";
2303  } elseif ( '#' === $char ) {
2304  $result .= "<ol>\n<li>";
2305  } elseif ( ':' === $char ) {
2306  $result .= "<dl>\n<dd>";
2307  } elseif ( ';' === $char ) {
2308  $result .= "<dl>\n<dt>";
2309  $this->mDTopen = true;
2310  } else {
2311  $result = '<!-- ERR 1 -->';
2312  }
2313 
2314  return $result;
2315  }
2316 
2324  function nextItem( $char ) {
2325  if ( '*' === $char || '#' === $char ) {
2326  return "</li>\n<li>";
2327  } elseif ( ':' === $char || ';' === $char ) {
2328  $close = "</dd>\n";
2329  if ( $this->mDTopen ) {
2330  $close = "</dt>\n";
2331  }
2332  if ( ';' === $char ) {
2333  $this->mDTopen = true;
2334  return $close . '<dt>';
2335  } else {
2336  $this->mDTopen = false;
2337  return $close . '<dd>';
2338  }
2339  }
2340  return '<!-- ERR 2 -->';
2341  }
2342 
2350  function closeList( $char ) {
2351  if ( '*' === $char ) {
2352  $text = "</li>\n</ul>";
2353  } elseif ( '#' === $char ) {
2354  $text = "</li>\n</ol>";
2355  } elseif ( ':' === $char ) {
2356  if ( $this->mDTopen ) {
2357  $this->mDTopen = false;
2358  $text = "</dt>\n</dl>";
2359  } else {
2360  $text = "</dd>\n</dl>";
2361  }
2362  } else {
2363  return '<!-- ERR 3 -->';
2364  }
2365  return $text . "\n";
2366  }
2377  function doBlockLevels( $text, $linestart ) {
2378  wfProfileIn( __METHOD__ );
2379 
2380  # Parsing through the text line by line. The main thing
2381  # happening here is handling of block-level elements p, pre,
2382  # and making lists from lines starting with * # : etc.
2383  #
2384  $textLines = StringUtils::explode( "\n", $text );
2385 
2386  $lastPrefix = $output = '';
2387  $this->mDTopen = $inBlockElem = false;
2388  $prefixLength = 0;
2389  $paragraphStack = false;
2390  $inBlockquote = false;
2391 
2392  foreach ( $textLines as $oLine ) {
2393  # Fix up $linestart
2394  if ( !$linestart ) {
2395  $output .= $oLine;
2396  $linestart = true;
2397  continue;
2398  }
2399  # * = ul
2400  # # = ol
2401  # ; = dt
2402  # : = dd
2403 
2404  $lastPrefixLength = strlen( $lastPrefix );
2405  $preCloseMatch = preg_match( '/<\\/pre/i', $oLine );
2406  $preOpenMatch = preg_match( '/<pre/i', $oLine );
2407  # If not in a <pre> element, scan for and figure out what prefixes are there.
2408  if ( !$this->mInPre ) {
2409  # Multiple prefixes may abut each other for nested lists.
2410  $prefixLength = strspn( $oLine, '*#:;' );
2411  $prefix = substr( $oLine, 0, $prefixLength );
2412 
2413  # eh?
2414  # ; and : are both from definition-lists, so they're equivalent
2415  # for the purposes of determining whether or not we need to open/close
2416  # elements.
2417  $prefix2 = str_replace( ';', ':', $prefix );
2418  $t = substr( $oLine, $prefixLength );
2419  $this->mInPre = (bool)$preOpenMatch;
2420  } else {
2421  # Don't interpret any other prefixes in preformatted text
2422  $prefixLength = 0;
2423  $prefix = $prefix2 = '';
2424  $t = $oLine;
2425  }
2426 
2427  # List generation
2428  if ( $prefixLength && $lastPrefix === $prefix2 ) {
2429  # Same as the last item, so no need to deal with nesting or opening stuff
2430  $output .= $this->nextItem( substr( $prefix, -1 ) );
2431  $paragraphStack = false;
2432 
2433  if ( substr( $prefix, -1 ) === ';' ) {
2434  # The one nasty exception: definition lists work like this:
2435  # ; title : definition text
2436  # So we check for : in the remainder text to split up the
2437  # title and definition, without b0rking links.
2438  $term = $t2 = '';
2439  if ( $this->findColonNoLinks( $t, $term, $t2 ) !== false ) {
2440  $t = $t2;
2441  $output .= $term . $this->nextItem( ':' );
2442  }
2443  }
2444  } elseif ( $prefixLength || $lastPrefixLength ) {
2445  # We need to open or close prefixes, or both.
2446 
2447  # Either open or close a level...
2448  $commonPrefixLength = $this->getCommon( $prefix, $lastPrefix );
2449  $paragraphStack = false;
2450 
2451  # Close all the prefixes which aren't shared.
2452  while ( $commonPrefixLength < $lastPrefixLength ) {
2453  $output .= $this->closeList( $lastPrefix[$lastPrefixLength - 1] );
2454  --$lastPrefixLength;
2455  }
2456 
2457  # Continue the current prefix if appropriate.
2458  if ( $prefixLength <= $commonPrefixLength && $commonPrefixLength > 0 ) {
2459  $output .= $this->nextItem( $prefix[$commonPrefixLength - 1] );
2460  }
2461 
2462  # Open prefixes where appropriate.
2463  while ( $prefixLength > $commonPrefixLength ) {
2464  $char = substr( $prefix, $commonPrefixLength, 1 );
2465  $output .= $this->openList( $char );
2466 
2467  if ( ';' === $char ) {
2468  # @todo FIXME: This is dupe of code above
2469  if ( $this->findColonNoLinks( $t, $term, $t2 ) !== false ) {
2470  $t = $t2;
2471  $output .= $term . $this->nextItem( ':' );
2472  }
2473  }
2474  ++$commonPrefixLength;
2475  }
2476  $lastPrefix = $prefix2;
2477  }
2478 
2479  # If we have no prefixes, go to paragraph mode.
2480  if ( 0 == $prefixLength ) {
2481  wfProfileIn( __METHOD__ . "-paragraph" );
2482  # No prefix (not in list)--go to paragraph mode
2483  # XXX: use a stack for nestable elements like span, table and div
2484  $openmatch = preg_match( '/(?:<table|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<ol|<dl|<li|<\\/tr|<\\/td|<\\/th)/iS', $t );
2485  $closematch = preg_match(
2486  '/(?:<\\/table|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|' .
2487  '<td|<th|<\\/?blockquote|<\\/?div|<hr|<\\/pre|<\\/p|<\\/mw:|' . $this->mUniqPrefix . '-pre|<\\/li|<\\/ul|<\\/ol|<\\/dl|<\\/?center)/iS', $t );
2488  if ( $openmatch or $closematch ) {
2489  $paragraphStack = false;
2490  # TODO bug 5718: paragraph closed
2491  $output .= $this->closeParagraph();
2492  if ( $preOpenMatch and !$preCloseMatch ) {
2493  $this->mInPre = true;
2494  }
2495  $bqOffset = 0;
2496  while ( preg_match( '/<(\\/?)blockquote[\s>]/i', $t, $bqMatch, PREG_OFFSET_CAPTURE, $bqOffset ) ) {
2497  $inBlockquote = !$bqMatch[1][0]; // is this a close tag?
2498  $bqOffset = $bqMatch[0][1] + strlen( $bqMatch[0][0] );
2499  }
2500  $inBlockElem = !$closematch;
2501  } elseif ( !$inBlockElem && !$this->mInPre ) {
2502  if ( ' ' == substr( $t, 0, 1 ) and ( $this->mLastSection === 'pre' || trim( $t ) != '' ) and !$inBlockquote ) {
2503  # pre
2504  if ( $this->mLastSection !== 'pre' ) {
2505  $paragraphStack = false;
2506  $output .= $this->closeParagraph() . '<pre>';
2507  $this->mLastSection = 'pre';
2508  }
2509  $t = substr( $t, 1 );
2510  } else {
2511  # paragraph
2512  if ( trim( $t ) === '' ) {
2513  if ( $paragraphStack ) {
2514  $output .= $paragraphStack . '<br />';
2515  $paragraphStack = false;
2516  $this->mLastSection = 'p';
2517  } else {
2518  if ( $this->mLastSection !== 'p' ) {
2519  $output .= $this->closeParagraph();
2520  $this->mLastSection = '';
2521  $paragraphStack = '<p>';
2522  } else {
2523  $paragraphStack = '</p><p>';
2524  }
2525  }
2526  } else {
2527  if ( $paragraphStack ) {
2528  $output .= $paragraphStack;
2529  $paragraphStack = false;
2530  $this->mLastSection = 'p';
2531  } elseif ( $this->mLastSection !== 'p' ) {
2532  $output .= $this->closeParagraph() . '<p>';
2533  $this->mLastSection = 'p';
2534  }
2535  }
2536  }
2537  }
2538  wfProfileOut( __METHOD__ . "-paragraph" );
2539  }
2540  # somewhere above we forget to get out of pre block (bug 785)
2541  if ( $preCloseMatch && $this->mInPre ) {
2542  $this->mInPre = false;
2543  }
2544  if ( $paragraphStack === false ) {
2545  $output .= $t . "\n";
2546  }
2547  }
2548  while ( $prefixLength ) {
2549  $output .= $this->closeList( $prefix2[$prefixLength - 1] );
2550  --$prefixLength;
2551  }
2552  if ( $this->mLastSection != '' ) {
2553  $output .= '</' . $this->mLastSection . '>';
2554  $this->mLastSection = '';
2555  }
2556 
2557  wfProfileOut( __METHOD__ );
2558  return $output;
2559  }
2560 
2571  function findColonNoLinks( $str, &$before, &$after ) {
2572  wfProfileIn( __METHOD__ );
2573 
2574  $pos = strpos( $str, ':' );
2575  if ( $pos === false ) {
2576  # Nothing to find!
2577  wfProfileOut( __METHOD__ );
2578  return false;
2579  }
2580 
2581  $lt = strpos( $str, '<' );
2582  if ( $lt === false || $lt > $pos ) {
2583  # Easy; no tag nesting to worry about
2584  $before = substr( $str, 0, $pos );
2585  $after = substr( $str, $pos + 1 );
2586  wfProfileOut( __METHOD__ );
2587  return $pos;
2588  }
2589 
2590  # Ugly state machine to walk through avoiding tags.
2591  $state = self::COLON_STATE_TEXT;
2592  $stack = 0;
2593  $len = strlen( $str );
2594  for ( $i = 0; $i < $len; $i++ ) {
2595  $c = $str[$i];
2596 
2597  switch ( $state ) {
2598  # (Using the number is a performance hack for common cases)
2599  case 0: # self::COLON_STATE_TEXT:
2600  switch ( $c ) {
2601  case "<":
2602  # Could be either a <start> tag or an </end> tag
2603  $state = self::COLON_STATE_TAGSTART;
2604  break;
2605  case ":":
2606  if ( $stack == 0 ) {
2607  # We found it!
2608  $before = substr( $str, 0, $i );
2609  $after = substr( $str, $i + 1 );
2610  wfProfileOut( __METHOD__ );
2611  return $i;
2612  }
2613  # Embedded in a tag; don't break it.
2614  break;
2615  default:
2616  # Skip ahead looking for something interesting
2617  $colon = strpos( $str, ':', $i );
2618  if ( $colon === false ) {
2619  # Nothing else interesting
2620  wfProfileOut( __METHOD__ );
2621  return false;
2622  }
2623  $lt = strpos( $str, '<', $i );
2624  if ( $stack === 0 ) {
2625  if ( $lt === false || $colon < $lt ) {
2626  # We found it!
2627  $before = substr( $str, 0, $colon );
2628  $after = substr( $str, $colon + 1 );
2629  wfProfileOut( __METHOD__ );
2630  return $i;
2631  }
2632  }
2633  if ( $lt === false ) {
2634  # Nothing else interesting to find; abort!
2635  # We're nested, but there's no close tags left. Abort!
2636  break 2;
2637  }
2638  # Skip ahead to next tag start
2639  $i = $lt;
2640  $state = self::COLON_STATE_TAGSTART;
2641  }
2642  break;
2643  case 1: # self::COLON_STATE_TAG:
2644  # In a <tag>
2645  switch ( $c ) {
2646  case ">":
2647  $stack++;
2648  $state = self::COLON_STATE_TEXT;
2649  break;
2650  case "/":
2651  # Slash may be followed by >?
2652  $state = self::COLON_STATE_TAGSLASH;
2653  break;
2654  default:
2655  # ignore
2656  }
2657  break;
2658  case 2: # self::COLON_STATE_TAGSTART:
2659  switch ( $c ) {
2660  case "/":
2661  $state = self::COLON_STATE_CLOSETAG;
2662  break;
2663  case "!":
2664  $state = self::COLON_STATE_COMMENT;
2665  break;
2666  case ">":
2667  # Illegal early close? This shouldn't happen D:
2668  $state = self::COLON_STATE_TEXT;
2669  break;
2670  default:
2671  $state = self::COLON_STATE_TAG;
2672  }
2673  break;
2674  case 3: # self::COLON_STATE_CLOSETAG:
2675  # In a </tag>
2676  if ( $c === ">" ) {
2677  $stack--;
2678  if ( $stack < 0 ) {
2679  wfDebug( __METHOD__ . ": Invalid input; too many close tags\n" );
2680  wfProfileOut( __METHOD__ );
2681  return false;
2682  }
2683  $state = self::COLON_STATE_TEXT;
2684  }
2685  break;
2686  case self::COLON_STATE_TAGSLASH:
2687  if ( $c === ">" ) {
2688  # Yes, a self-closed tag <blah/>
2689  $state = self::COLON_STATE_TEXT;
2690  } else {
2691  # Probably we're jumping the gun, and this is an attribute
2692  $state = self::COLON_STATE_TAG;
2693  }
2694  break;
2695  case 5: # self::COLON_STATE_COMMENT:
2696  if ( $c === "-" ) {
2697  $state = self::COLON_STATE_COMMENTDASH;
2698  }
2699  break;
2700  case self::COLON_STATE_COMMENTDASH:
2701  if ( $c === "-" ) {
2702  $state = self::COLON_STATE_COMMENTDASHDASH;
2703  } else {
2704  $state = self::COLON_STATE_COMMENT;
2705  }
2706  break;
2707  case self::COLON_STATE_COMMENTDASHDASH:
2708  if ( $c === ">" ) {
2709  $state = self::COLON_STATE_TEXT;
2710  } else {
2711  $state = self::COLON_STATE_COMMENT;
2712  }
2713  break;
2714  default:
2715  wfProfileOut( __METHOD__ );
2716  throw new MWException( "State machine error in " . __METHOD__ );
2717  }
2718  }
2719  if ( $stack > 0 ) {
2720  wfDebug( __METHOD__ . ": Invalid input; not enough close tags (stack $stack, state $state)\n" );
2721  wfProfileOut( __METHOD__ );
2722  return false;
2723  }
2724  wfProfileOut( __METHOD__ );
2725  return false;
2726  }
2727 
2739  function getVariableValue( $index, $frame = false ) {
2740  global $wgContLang, $wgSitename, $wgServer;
2741  global $wgArticlePath, $wgScriptPath, $wgStylePath;
2742 
2743  if ( is_null( $this->mTitle ) ) {
2744  // If no title set, bad things are going to happen
2745  // later. Title should always be set since this
2746  // should only be called in the middle of a parse
2747  // operation (but the unit-tests do funky stuff)
2748  throw new MWException( __METHOD__ . ' Should only be '
2749  . ' called while parsing (no title set)' );
2750  }
2751 
2756  if ( wfRunHooks( 'ParserGetVariableValueVarCache', array( &$this, &$this->mVarCache ) ) ) {
2757  if ( isset( $this->mVarCache[$index] ) ) {
2758  return $this->mVarCache[$index];
2759  }
2760  }
2761 
2762  $ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
2763  wfRunHooks( 'ParserGetVariableValueTs', array( &$this, &$ts ) );
2764 
2765  $pageLang = $this->getFunctionLang();
2766 
2767  switch ( $index ) {
2768  case 'currentmonth':
2769  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'm' ) );
2770  break;
2771  case 'currentmonth1':
2772  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2773  break;
2774  case 'currentmonthname':
2775  $value = $pageLang->getMonthName( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2776  break;
2777  case 'currentmonthnamegen':
2778  $value = $pageLang->getMonthNameGen( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2779  break;
2780  case 'currentmonthabbrev':
2781  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2782  break;
2783  case 'currentday':
2784  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'j' ) );
2785  break;
2786  case 'currentday2':
2787  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'd' ) );
2788  break;
2789  case 'localmonth':
2790  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'm' ) );
2791  break;
2792  case 'localmonth1':
2793  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2794  break;
2795  case 'localmonthname':
2796  $value = $pageLang->getMonthName( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2797  break;
2798  case 'localmonthnamegen':
2799  $value = $pageLang->getMonthNameGen( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2800  break;
2801  case 'localmonthabbrev':
2802  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2803  break;
2804  case 'localday':
2805  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'j' ) );
2806  break;
2807  case 'localday2':
2808  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'd' ) );
2809  break;
2810  case 'pagename':
2811  $value = wfEscapeWikiText( $this->mTitle->getText() );
2812  break;
2813  case 'pagenamee':
2814  $value = wfEscapeWikiText( $this->mTitle->getPartialURL() );
2815  break;
2816  case 'fullpagename':
2817  $value = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
2818  break;
2819  case 'fullpagenamee':
2820  $value = wfEscapeWikiText( $this->mTitle->getPrefixedURL() );
2821  break;
2822  case 'subpagename':
2823  $value = wfEscapeWikiText( $this->mTitle->getSubpageText() );
2824  break;
2825  case 'subpagenamee':
2826  $value = wfEscapeWikiText( $this->mTitle->getSubpageUrlForm() );
2827  break;
2828  case 'rootpagename':
2829  $value = wfEscapeWikiText( $this->mTitle->getRootText() );
2830  break;
2831  case 'rootpagenamee':
2832  $value = wfEscapeWikiText( wfUrlEncode( str_replace( ' ', '_', $this->mTitle->getRootText() ) ) );
2833  break;
2834  case 'basepagename':
2835  $value = wfEscapeWikiText( $this->mTitle->getBaseText() );
2836  break;
2837  case 'basepagenamee':
2838  $value = wfEscapeWikiText( wfUrlEncode( str_replace( ' ', '_', $this->mTitle->getBaseText() ) ) );
2839  break;
2840  case 'talkpagename':
2841  if ( $this->mTitle->canTalk() ) {
2842  $talkPage = $this->mTitle->getTalkPage();
2843  $value = wfEscapeWikiText( $talkPage->getPrefixedText() );
2844  } else {
2845  $value = '';
2846  }
2847  break;
2848  case 'talkpagenamee':
2849  if ( $this->mTitle->canTalk() ) {
2850  $talkPage = $this->mTitle->getTalkPage();
2851  $value = wfEscapeWikiText( $talkPage->getPrefixedURL() );
2852  } else {
2853  $value = '';
2854  }
2855  break;
2856  case 'subjectpagename':
2857  $subjPage = $this->mTitle->getSubjectPage();
2858  $value = wfEscapeWikiText( $subjPage->getPrefixedText() );
2859  break;
2860  case 'subjectpagenamee':
2861  $subjPage = $this->mTitle->getSubjectPage();
2862  $value = wfEscapeWikiText( $subjPage->getPrefixedURL() );
2863  break;
2864  case 'pageid': // requested in bug 23427
2865  $pageid = $this->getTitle()->getArticleID();
2866  if ( $pageid == 0 ) {
2867  # 0 means the page doesn't exist in the database,
2868  # which means the user is previewing a new page.
2869  # The vary-revision flag must be set, because the magic word
2870  # will have a different value once the page is saved.
2871  $this->mOutput->setFlag( 'vary-revision' );
2872  wfDebug( __METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision...\n" );
2873  }
2874  $value = $pageid ? $pageid : null;
2875  break;
2876  case 'revisionid':
2877  # Let the edit saving system know we should parse the page
2878  # *after* a revision ID has been assigned.
2879  $this->mOutput->setFlag( 'vary-revision' );
2880  wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision...\n" );
2881  $value = $this->mRevisionId;
2882  break;
2883  case 'revisionday':
2884  # Let the edit saving system know we should parse the page
2885  # *after* a revision ID has been assigned. This is for null edits.
2886  $this->mOutput->setFlag( 'vary-revision' );
2887  wfDebug( __METHOD__ . ": {{REVISIONDAY}} used, setting vary-revision...\n" );
2888  $value = intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
2889  break;
2890  case 'revisionday2':
2891  # Let the edit saving system know we should parse the page
2892  # *after* a revision ID has been assigned. This is for null edits.
2893  $this->mOutput->setFlag( 'vary-revision' );
2894  wfDebug( __METHOD__ . ": {{REVISIONDAY2}} used, setting vary-revision...\n" );
2895  $value = substr( $this->getRevisionTimestamp(), 6, 2 );
2896  break;
2897  case 'revisionmonth':
2898  # Let the edit saving system know we should parse the page
2899  # *after* a revision ID has been assigned. This is for null edits.
2900  $this->mOutput->setFlag( 'vary-revision' );
2901  wfDebug( __METHOD__ . ": {{REVISIONMONTH}} used, setting vary-revision...\n" );
2902  $value = substr( $this->getRevisionTimestamp(), 4, 2 );
2903  break;
2904  case 'revisionmonth1':
2905  # Let the edit saving system know we should parse the page
2906  # *after* a revision ID has been assigned. This is for null edits.
2907  $this->mOutput->setFlag( 'vary-revision' );
2908  wfDebug( __METHOD__ . ": {{REVISIONMONTH1}} used, setting vary-revision...\n" );
2909  $value = intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
2910  break;
2911  case 'revisionyear':
2912  # Let the edit saving system know we should parse the page
2913  # *after* a revision ID has been assigned. This is for null edits.
2914  $this->mOutput->setFlag( 'vary-revision' );
2915  wfDebug( __METHOD__ . ": {{REVISIONYEAR}} used, setting vary-revision...\n" );
2916  $value = substr( $this->getRevisionTimestamp(), 0, 4 );
2917  break;
2918  case 'revisiontimestamp':
2919  # Let the edit saving system know we should parse the page
2920  # *after* a revision ID has been assigned. This is for null edits.
2921  $this->mOutput->setFlag( 'vary-revision' );
2922  wfDebug( __METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" );
2923  $value = $this->getRevisionTimestamp();
2924  break;
2925  case 'revisionuser':
2926  # Let the edit saving system know we should parse the page
2927  # *after* a revision ID has been assigned. This is for null edits.
2928  $this->mOutput->setFlag( 'vary-revision' );
2929  wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-revision...\n" );
2930  $value = $this->getRevisionUser();
2931  break;
2932  case 'revisionsize':
2933  # Let the edit saving system know we should parse the page
2934  # *after* a revision ID has been assigned. This is for null edits.
2935  $this->mOutput->setFlag( 'vary-revision' );
2936  wfDebug( __METHOD__ . ": {{REVISIONSIZE}} used, setting vary-revision...\n" );
2937  $value = $this->getRevisionSize();
2938  break;
2939  case 'namespace':
2940  $value = str_replace( '_', ' ', $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2941  break;
2942  case 'namespacee':
2943  $value = wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2944  break;
2945  case 'namespacenumber':
2946  $value = $this->mTitle->getNamespace();
2947  break;
2948  case 'talkspace':
2949  $value = $this->mTitle->canTalk() ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() ) : '';
2950  break;
2951  case 'talkspacee':
2952  $value = $this->mTitle->canTalk() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
2953  break;
2954  case 'subjectspace':
2955  $value = str_replace( '_', ' ', $this->mTitle->getSubjectNsText() );
2956  break;
2957  case 'subjectspacee':
2958  $value = ( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
2959  break;
2960  case 'currentdayname':
2961  $value = $pageLang->getWeekdayName( (int)MWTimestamp::getInstance( $ts )->format( 'w' ) + 1 );
2962  break;
2963  case 'currentyear':
2964  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'Y' ), true );
2965  break;
2966  case 'currenttime':
2967  $value = $pageLang->time( wfTimestamp( TS_MW, $ts ), false, false );
2968  break;
2969  case 'currenthour':
2970  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'H' ), true );
2971  break;
2972  case 'currentweek':
2973  # @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
2974  # int to remove the padding
2975  $value = $pageLang->formatNum( (int)MWTimestamp::getInstance( $ts )->format( 'W' ) );
2976  break;
2977  case 'currentdow':
2978  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'w' ) );
2979  break;
2980  case 'localdayname':
2981  $value = $pageLang->getWeekdayName( (int)MWTimestamp::getLocalInstance( $ts )->format( 'w' ) + 1 );
2982  break;
2983  case 'localyear':
2984  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'Y' ), true );
2985  break;
2986  case 'localtime':
2987  $value = $pageLang->time( MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' ), false, false );
2988  break;
2989  case 'localhour':
2990  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'H' ), true );
2991  break;
2992  case 'localweek':
2993  # @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
2994  # int to remove the padding
2995  $value = $pageLang->formatNum( (int)MWTimestamp::getLocalInstance( $ts )->format( 'W' ) );
2996  break;
2997  case 'localdow':
2998  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'w' ) );
2999  break;
3000  case 'numberofarticles':
3001  $value = $pageLang->formatNum( SiteStats::articles() );
3002  break;
3003  case 'numberoffiles':
3004  $value = $pageLang->formatNum( SiteStats::images() );
3005  break;
3006  case 'numberofusers':
3007  $value = $pageLang->formatNum( SiteStats::users() );
3008  break;
3009  case 'numberofactiveusers':
3010  $value = $pageLang->formatNum( SiteStats::activeUsers() );
3011  break;
3012  case 'numberofpages':
3013  $value = $pageLang->formatNum( SiteStats::pages() );
3014  break;
3015  case 'numberofadmins':
3016  $value = $pageLang->formatNum( SiteStats::numberingroup( 'sysop' ) );
3017  break;
3018  case 'numberofedits':
3019  $value = $pageLang->formatNum( SiteStats::edits() );
3020  break;
3021  case 'numberofviews':
3022  global $wgDisableCounters;
3023  $value = !$wgDisableCounters ? $pageLang->formatNum( SiteStats::views() ) : '';
3024  break;
3025  case 'currenttimestamp':
3026  $value = wfTimestamp( TS_MW, $ts );
3027  break;
3028  case 'localtimestamp':
3029  $value = MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' );
3030  break;
3031  case 'currentversion':
3033  break;
3034  case 'articlepath':
3035  return $wgArticlePath;
3036  case 'sitename':
3037  return $wgSitename;
3038  case 'server':
3039  return $wgServer;
3040  case 'servername':
3041  $serverParts = wfParseUrl( $wgServer );
3042  return $serverParts && isset( $serverParts['host'] ) ? $serverParts['host'] : $wgServer;
3043  case 'scriptpath':
3044  return $wgScriptPath;
3045  case 'stylepath':
3046  return $wgStylePath;
3047  case 'directionmark':
3048  return $pageLang->getDirMark();
3049  case 'contentlanguage':
3050  global $wgLanguageCode;
3051  return $wgLanguageCode;
3052  case 'cascadingsources':
3054  break;
3055  default:
3056  $ret = null;
3057  wfRunHooks( 'ParserGetVariableValueSwitch', array( &$this, &$this->mVarCache, &$index, &$ret, &$frame ) );
3058  return $ret;
3059  }
3060 
3061  if ( $index ) {
3062  $this->mVarCache[$index] = $value;
3063  }
3064 
3065  return $value;
3066  }
3067 
3073  function initialiseVariables() {
3074  wfProfileIn( __METHOD__ );
3075  $variableIDs = MagicWord::getVariableIDs();
3076  $substIDs = MagicWord::getSubstIDs();
3077 
3078  $this->mVariables = new MagicWordArray( $variableIDs );
3079  $this->mSubstWords = new MagicWordArray( $substIDs );
3080  wfProfileOut( __METHOD__ );
3081  }
3082 
3105  function preprocessToDom( $text, $flags = 0 ) {
3106  $dom = $this->getPreprocessor()->preprocessToObj( $text, $flags );
3107  return $dom;
3108  }
3109 
3117  public static function splitWhitespace( $s ) {
3118  $ltrimmed = ltrim( $s );
3119  $w1 = substr( $s, 0, strlen( $s ) - strlen( $ltrimmed ) );
3120  $trimmed = rtrim( $ltrimmed );
3121  $diff = strlen( $ltrimmed ) - strlen( $trimmed );
3122  if ( $diff > 0 ) {
3123  $w2 = substr( $ltrimmed, -$diff );
3124  } else {
3125  $w2 = '';
3126  }
3127  return array( $w1, $trimmed, $w2 );
3128  }
3129 
3149  function replaceVariables( $text, $frame = false, $argsOnly = false ) {
3150  # Is there any text? Also, Prevent too big inclusions!
3151  if ( strlen( $text ) < 1 || strlen( $text ) > $this->mOptions->getMaxIncludeSize() ) {
3152  return $text;
3153  }
3154  wfProfileIn( __METHOD__ );
3155 
3156  if ( $frame === false ) {
3157  $frame = $this->getPreprocessor()->newFrame();
3158  } elseif ( !( $frame instanceof PPFrame ) ) {
3159  wfDebug( __METHOD__ . " called using plain parameters instead of a PPFrame instance. Creating custom frame.\n" );
3160  $frame = $this->getPreprocessor()->newCustomFrame( $frame );
3161  }
3162 
3163  $dom = $this->preprocessToDom( $text );
3164  $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
3165  $text = $frame->expand( $dom, $flags );
3166 
3167  wfProfileOut( __METHOD__ );
3168  return $text;
3169  }
3170 
3178  static function createAssocArgs( $args ) {
3179  $assocArgs = array();
3180  $index = 1;
3181  foreach ( $args as $arg ) {
3182  $eqpos = strpos( $arg, '=' );
3183  if ( $eqpos === false ) {
3184  $assocArgs[$index++] = $arg;
3185  } else {
3186  $name = trim( substr( $arg, 0, $eqpos ) );
3187  $value = trim( substr( $arg, $eqpos + 1 ) );
3188  if ( $value === false ) {
3189  $value = '';
3190  }
3191  if ( $name !== false ) {
3192  $assocArgs[$name] = $value;
3193  }
3194  }
3195  }
3196 
3197  return $assocArgs;
3198  }
3199 
3224  function limitationWarn( $limitationType, $current = '', $max = '' ) {
3225  # does no harm if $current and $max are present but are unnecessary for the message
3226  $warning = wfMessage( "$limitationType-warning" )->numParams( $current, $max )
3227  ->inLanguage( $this->mOptions->getUserLangObj() )->text();
3228  $this->mOutput->addWarning( $warning );
3229  $this->addTrackingCategory( "$limitationType-category" );
3230  }
3231 
3245  function braceSubstitution( $piece, $frame ) {
3246  wfProfileIn( __METHOD__ );
3247  wfProfileIn( __METHOD__ . '-setup' );
3248 
3249  # Flags
3250  $found = false; # $text has been filled
3251  $nowiki = false; # wiki markup in $text should be escaped
3252  $isHTML = false; # $text is HTML, armour it against wikitext transformation
3253  $forceRawInterwiki = false; # Force interwiki transclusion to be done in raw mode not rendered
3254  $isChildObj = false; # $text is a DOM node needing expansion in a child frame
3255  $isLocalObj = false; # $text is a DOM node needing expansion in the current frame
3256 
3257  # Title object, where $text came from
3258  $title = false;
3259 
3260  # $part1 is the bit before the first |, and must contain only title characters.
3261  # Various prefixes will be stripped from it later.
3262  $titleWithSpaces = $frame->expand( $piece['title'] );
3263  $part1 = trim( $titleWithSpaces );
3264  $titleText = false;
3265 
3266  # Original title text preserved for various purposes
3267  $originalTitle = $part1;
3268 
3269  # $args is a list of argument nodes, starting from index 0, not including $part1
3270  # @todo FIXME: If piece['parts'] is null then the call to getLength() below won't work b/c this $args isn't an object
3271  $args = ( null == $piece['parts'] ) ? array() : $piece['parts'];
3272  wfProfileOut( __METHOD__ . '-setup' );
3273 
3274  $titleProfileIn = null; // profile templates
3275 
3276  # SUBST
3277  wfProfileIn( __METHOD__ . '-modifiers' );
3278  if ( !$found ) {
3279 
3280  $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3281 
3282  # Possibilities for substMatch: "subst", "safesubst" or FALSE
3283  # Decide whether to expand template or keep wikitext as-is.
3284  if ( $this->ot['wiki'] ) {
3285  if ( $substMatch === false ) {
3286  $literal = true; # literal when in PST with no prefix
3287  } else {
3288  $literal = false; # expand when in PST with subst: or safesubst:
3289  }
3290  } else {
3291  if ( $substMatch == 'subst' ) {
3292  $literal = true; # literal when not in PST with plain subst:
3293  } else {
3294  $literal = false; # expand when not in PST with safesubst: or no prefix
3295  }
3296  }
3297  if ( $literal ) {
3298  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3299  $isLocalObj = true;
3300  $found = true;
3301  }
3302  }
3303 
3304  # Variables
3305  if ( !$found && $args->getLength() == 0 ) {
3306  $id = $this->mVariables->matchStartToEnd( $part1 );
3307  if ( $id !== false ) {
3308  $text = $this->getVariableValue( $id, $frame );
3309  if ( MagicWord::getCacheTTL( $id ) > -1 ) {
3310  $this->mOutput->updateCacheExpiry( MagicWord::getCacheTTL( $id ) );
3311  }
3312  $found = true;
3313  }
3314  }
3315 
3316  # MSG, MSGNW and RAW
3317  if ( !$found ) {
3318  # Check for MSGNW:
3319  $mwMsgnw = MagicWord::get( 'msgnw' );
3320  if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3321  $nowiki = true;
3322  } else {
3323  # Remove obsolete MSG:
3324  $mwMsg = MagicWord::get( 'msg' );
3325  $mwMsg->matchStartAndRemove( $part1 );
3326  }
3327 
3328  # Check for RAW:
3329  $mwRaw = MagicWord::get( 'raw' );
3330  if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3331  $forceRawInterwiki = true;
3332  }
3333  }
3334  wfProfileOut( __METHOD__ . '-modifiers' );
3335 
3336  # Parser functions
3337  if ( !$found ) {
3338  wfProfileIn( __METHOD__ . '-pfunc' );
3339 
3340  $colonPos = strpos( $part1, ':' );
3341  if ( $colonPos !== false ) {
3342  $func = substr( $part1, 0, $colonPos );
3343  $funcArgs = array( trim( substr( $part1, $colonPos + 1 ) ) );
3344  for ( $i = 0; $i < $args->getLength(); $i++ ) {
3345  $funcArgs[] = $args->item( $i );
3346  }
3347  try {
3348  $result = $this->callParserFunction( $frame, $func, $funcArgs );
3349  } catch ( Exception $ex ) {
3350  wfProfileOut( __METHOD__ . '-pfunc' );
3351  wfProfileOut( __METHOD__ );
3352  throw $ex;
3353  }
3354 
3355  # The interface for parser functions allows for extracting
3356  # flags into the local scope. Extract any forwarded flags
3357  # here.
3358  extract( $result );
3359  }
3360  wfProfileOut( __METHOD__ . '-pfunc' );
3361  }
3362 
3363  # Finish mangling title and then check for loops.
3364  # Set $title to a Title object and $titleText to the PDBK
3365  if ( !$found ) {
3366  $ns = NS_TEMPLATE;
3367  # Split the title into page and subpage
3368  $subpage = '';
3369  $relative = $this->maybeDoSubpageLink( $part1, $subpage );
3370  if ( $part1 !== $relative ) {
3371  $part1 = $relative;
3372  $ns = $this->mTitle->getNamespace();
3373  }
3374  $title = Title::newFromText( $part1, $ns );
3375  if ( $title ) {
3376  $titleText = $title->getPrefixedText();
3377  # Check for language variants if the template is not found
3378  if ( $this->getConverterLanguage()->hasVariants() && $title->getArticleID() == 0 ) {
3379  $this->getConverterLanguage()->findVariantLink( $part1, $title, true );
3380  }
3381  # Do recursion depth check
3382  $limit = $this->mOptions->getMaxTemplateDepth();
3383  if ( $frame->depth >= $limit ) {
3384  $found = true;
3385  $text = '<span class="error">'
3386  . wfMessage( 'parser-template-recursion-depth-warning' )
3387  ->numParams( $limit )->inContentLanguage()->text()
3388  . '</span>';
3389  }
3390  }
3391  }
3392 
3393  # Load from database
3394  if ( !$found && $title ) {
3395  if ( !Profiler::instance()->isPersistent() ) {
3396  # Too many unique items can kill profiling DBs/collectors
3397  $titleProfileIn = __METHOD__ . "-title-" . $title->getPrefixedDBkey();
3398  wfProfileIn( $titleProfileIn ); // template in
3399  }
3400  wfProfileIn( __METHOD__ . '-loadtpl' );
3401  if ( !$title->isExternal() ) {
3402  if ( $title->isSpecialPage()
3403  && $this->mOptions->getAllowSpecialInclusion()
3404  && $this->ot['html']
3405  ) {
3406  // Pass the template arguments as URL parameters.
3407  // "uselang" will have no effect since the Language object
3408  // is forced to the one defined in ParserOptions.
3409  $pageArgs = array();
3410  for ( $i = 0; $i < $args->getLength(); $i++ ) {
3411  $bits = $args->item( $i )->splitArg();
3412  if ( strval( $bits['index'] ) === '' ) {
3413  $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
3414  $value = trim( $frame->expand( $bits['value'] ) );
3415  $pageArgs[$name] = $value;
3416  }
3417  }
3418 
3419  // Create a new context to execute the special page
3420  $context = new RequestContext;
3421  $context->setTitle( $title );
3422  $context->setRequest( new FauxRequest( $pageArgs ) );
3423  $context->setUser( $this->getUser() );
3424  $context->setLanguage( $this->mOptions->getUserLangObj() );
3426  if ( $ret ) {
3427  $text = $context->getOutput()->getHTML();
3428  $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3429  $found = true;
3430  $isHTML = true;
3431  $this->disableCache();
3432  }
3433  } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
3434  $found = false; # access denied
3435  wfDebug( __METHOD__ . ": template inclusion denied for " .
3436  $title->getPrefixedDBkey() . "\n" );
3437  } else {
3438  list( $text, $title ) = $this->getTemplateDom( $title );
3439  if ( $text !== false ) {
3440  $found = true;
3441  $isChildObj = true;
3442  }
3443  }
3444 
3445  # If the title is valid but undisplayable, make a link to it
3446  if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3447  $text = "[[:$titleText]]";
3448  $found = true;
3449  }
3450  } elseif ( $title->isTrans() ) {
3451  # Interwiki transclusion
3452  if ( $this->ot['html'] && !$forceRawInterwiki ) {
3453  $text = $this->interwikiTransclude( $title, 'render' );
3454  $isHTML = true;
3455  } else {
3456  $text = $this->interwikiTransclude( $title, 'raw' );
3457  # Preprocess it like a template
3458  $text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3459  $isChildObj = true;
3460  }
3461  $found = true;
3462  }
3463 
3464  # Do infinite loop check
3465  # This has to be done after redirect resolution to avoid infinite loops via redirects
3466  if ( !$frame->loopCheck( $title ) ) {
3467  $found = true;
3468  $text = '<span class="error">'
3469  . wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3470  . '</span>';
3471  wfDebug( __METHOD__ . ": template loop broken at '$titleText'\n" );
3472  }
3473  wfProfileOut( __METHOD__ . '-loadtpl' );
3474  }
3475 
3476  # If we haven't found text to substitute by now, we're done
3477  # Recover the source wikitext and return it
3478  if ( !$found ) {
3479  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3480  if ( $titleProfileIn ) {
3481  wfProfileOut( $titleProfileIn ); // template out
3482  }
3483  wfProfileOut( __METHOD__ );
3484  return array( 'object' => $text );
3485  }
3486 
3487  # Expand DOM-style return values in a child frame
3488  if ( $isChildObj ) {
3489  # Clean up argument array
3490  $newFrame = $frame->newChild( $args, $title );
3491 
3492  if ( $nowiki ) {
3493  $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3494  } elseif ( $titleText !== false && $newFrame->isEmpty() ) {
3495  # Expansion is eligible for the empty-frame cache
3496  if ( isset( $this->mTplExpandCache[$titleText] ) ) {
3497  $text = $this->mTplExpandCache[$titleText];
3498  } else {
3499  $text = $newFrame->expand( $text );
3500  $this->mTplExpandCache[$titleText] = $text;
3501  }
3502  } else {
3503  # Uncached expansion
3504  $text = $newFrame->expand( $text );
3505  }
3506  }
3507  if ( $isLocalObj && $nowiki ) {
3508  $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
3509  $isLocalObj = false;
3510  }
3511 
3512  if ( $titleProfileIn ) {
3513  wfProfileOut( $titleProfileIn ); // template out
3514  }
3515 
3516  # Replace raw HTML by a placeholder
3517  if ( $isHTML ) {
3518  $text = $this->insertStripItem( $text );
3519  } elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3520  # Escape nowiki-style return values
3521  $text = wfEscapeWikiText( $text );
3522  } elseif ( is_string( $text )
3523  && !$piece['lineStart']
3524  && preg_match( '/^(?:{\\||:|;|#|\*)/', $text )
3525  ) {
3526  # Bug 529: if the template begins with a table or block-level
3527  # element, it should be treated as beginning a new line.
3528  # This behavior is somewhat controversial.
3529  $text = "\n" . $text;
3530  }
3531 
3532  if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
3533  # Error, oversize inclusion
3534  if ( $titleText !== false ) {
3535  # Make a working, properly escaped link if possible (bug 23588)
3536  $text = "[[:$titleText]]";
3537  } else {
3538  # This will probably not be a working link, but at least it may
3539  # provide some hint of where the problem is
3540  preg_replace( '/^:/', '', $originalTitle );
3541  $text = "[[:$originalTitle]]";
3542  }
3543  $text .= $this->insertStripItem( '<!-- WARNING: template omitted, post-expand include size too large -->' );
3544  $this->limitationWarn( 'post-expand-template-inclusion' );
3545  }
3546 
3547  if ( $isLocalObj ) {
3548  $ret = array( 'object' => $text );
3549  } else {
3550  $ret = array( 'text' => $text );
3551  }
3552 
3553  wfProfileOut( __METHOD__ );
3554  return $ret;
3555  }
3556 
3575  public function callParserFunction( $frame, $function, array $args = array() ) {
3577 
3578  wfProfileIn( __METHOD__ );
3579 
3580  # Case sensitive functions
3581  if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3582  $function = $this->mFunctionSynonyms[1][$function];
3583  } else {
3584  # Case insensitive functions
3585  $function = $wgContLang->lc( $function );
3586  if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3587  $function = $this->mFunctionSynonyms[0][$function];
3588  } else {
3589  wfProfileOut( __METHOD__ );
3590  return array( 'found' => false );
3591  }
3592  }
3593 
3594  wfProfileIn( __METHOD__ . '-pfunc-' . $function );
3595  list( $callback, $flags ) = $this->mFunctionHooks[$function];
3596 
3597  # Workaround for PHP bug 35229 and similar
3598  if ( !is_callable( $callback ) ) {
3599  wfProfileOut( __METHOD__ . '-pfunc-' . $function );
3600  wfProfileOut( __METHOD__ );
3601  throw new MWException( "Tag hook for $function is not callable\n" );
3602  }
3603 
3604  $allArgs = array( &$this );
3605  if ( $flags & SFH_OBJECT_ARGS ) {
3606  # Convert arguments to PPNodes and collect for appending to $allArgs
3607  $funcArgs = array();
3608  foreach ( $args as $k => $v ) {
3609  if ( $v instanceof PPNode || $k === 0 ) {
3610  $funcArgs[] = $v;
3611  } else {
3612  $funcArgs[] = $this->mPreprocessor->newPartNodeArray( array( $k => $v ) )->item( 0 );
3613  }
3614  }
3615 
3616  # Add a frame parameter, and pass the arguments as an array
3617  $allArgs[] = $frame;
3618  $allArgs[] = $funcArgs;
3619  } else {
3620  # Convert arguments to plain text and append to $allArgs
3621  foreach ( $args as $k => $v ) {
3622  if ( $v instanceof PPNode ) {
3623  $allArgs[] = trim( $frame->expand( $v ) );
3624  } elseif ( is_int( $k ) && $k >= 0 ) {
3625  $allArgs[] = trim( $v );
3626  } else {
3627  $allArgs[] = trim( "$k=$v" );
3628  }
3629  }
3630  }
3631 
3632  $result = call_user_func_array( $callback, $allArgs );
3633 
3634  # The interface for function hooks allows them to return a wikitext
3635  # string or an array containing the string and any flags. This mungs
3636  # things around to match what this method should return.
3637  if ( !is_array( $result ) ) {
3638  $result = array(
3639  'found' => true,
3640  'text' => $result,
3641  );
3642  } else {
3643  if ( isset( $result[0] ) && !isset( $result['text'] ) ) {
3644  $result['text'] = $result[0];
3645  }
3646  unset( $result[0] );
3647  $result += array(
3648  'found' => true,
3649  );
3650  }
3651 
3652  $noparse = true;
3653  $preprocessFlags = 0;
3654  if ( isset( $result['noparse'] ) ) {
3655  $noparse = $result['noparse'];
3656  }
3657  if ( isset( $result['preprocessFlags'] ) ) {
3658  $preprocessFlags = $result['preprocessFlags'];
3659  }
3660 
3661  if ( !$noparse ) {
3662  $result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
3663  $result['isChildObj'] = true;
3664  }
3665  wfProfileOut( __METHOD__ . '-pfunc-' . $function );
3666  wfProfileOut( __METHOD__ );
3667 
3668  return $result;
3669  }
3670 
3679  function getTemplateDom( $title ) {
3680  $cacheTitle = $title;
3681  $titleText = $title->getPrefixedDBkey();
3682 
3683  if ( isset( $this->mTplRedirCache[$titleText] ) ) {
3684  list( $ns, $dbk ) = $this->mTplRedirCache[$titleText];
3685  $title = Title::makeTitle( $ns, $dbk );
3686  $titleText = $title->getPrefixedDBkey();
3687  }
3688  if ( isset( $this->mTplDomCache[$titleText] ) ) {
3689  return array( $this->mTplDomCache[$titleText], $title );
3690  }
3691 
3692  # Cache miss, go to the database
3693  list( $text, $title ) = $this->fetchTemplateAndTitle( $title );
3694 
3695  if ( $text === false ) {
3696  $this->mTplDomCache[$titleText] = false;
3697  return array( false, $title );
3698  }
3699 
3700  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3701  $this->mTplDomCache[$titleText] = $dom;
3702 
3703  if ( !$title->equals( $cacheTitle ) ) {
3704  $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
3705  array( $title->getNamespace(), $cdb = $title->getDBkey() );
3706  }
3707 
3708  return array( $dom, $title );
3709  }
3710 
3716  function fetchTemplateAndTitle( $title ) {
3717  $templateCb = $this->mOptions->getTemplateCallback(); # Defaults to Parser::statelessFetchTemplate()
3718  $stuff = call_user_func( $templateCb, $title, $this );
3719  $text = $stuff['text'];
3720  $finalTitle = isset( $stuff['finalTitle'] ) ? $stuff['finalTitle'] : $title;
3721  if ( isset( $stuff['deps'] ) ) {
3722  foreach ( $stuff['deps'] as $dep ) {
3723  $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
3724  if ( $dep['title']->equals( $this->getTitle() ) ) {
3725  // If we transclude ourselves, the final result
3726  // will change based on the new version of the page
3727  $this->mOutput->setFlag( 'vary-revision' );
3728  }
3729  }
3730  }
3731  return array( $text, $finalTitle );
3732  }
3733 
3739  function fetchTemplate( $title ) {
3740  $rv = $this->fetchTemplateAndTitle( $title );
3741  return $rv[0];
3742  }
3743 
3753  static function statelessFetchTemplate( $title, $parser = false ) {
3754  $text = $skip = false;
3755  $finalTitle = $title;
3756  $deps = array();
3757 
3758  # Loop to fetch the article, with up to 1 redirect
3759  for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
3760  # Give extensions a chance to select the revision instead
3761  $id = false; # Assume current
3762  wfRunHooks( 'BeforeParserFetchTemplateAndtitle',
3763  array( $parser, $title, &$skip, &$id ) );
3764 
3765  if ( $skip ) {
3766  $text = false;
3767  $deps[] = array(
3768  'title' => $title,
3769  'page_id' => $title->getArticleID(),
3770  'rev_id' => null
3771  );
3772  break;
3773  }
3774  # Get the revision
3775  $rev = $id
3776  ? Revision::newFromId( $id )
3777  : Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
3778  $rev_id = $rev ? $rev->getId() : 0;
3779  # If there is no current revision, there is no page
3780  if ( $id === false && !$rev ) {
3781  $linkCache = LinkCache::singleton();
3782  $linkCache->addBadLinkObj( $title );
3783  }
3784 
3785  $deps[] = array(
3786  'title' => $title,
3787  'page_id' => $title->getArticleID(),
3788  'rev_id' => $rev_id );
3789  if ( $rev && !$title->equals( $rev->getTitle() ) ) {
3790  # We fetched a rev from a different title; register it too...
3791  $deps[] = array(
3792  'title' => $rev->getTitle(),
3793  'page_id' => $rev->getPage(),
3794  'rev_id' => $rev_id );
3795  }
3796 
3797  if ( $rev ) {
3798  $content = $rev->getContent();
3799  $text = $content ? $content->getWikitextForTransclusion() : null;
3800 
3801  if ( $text === false || $text === null ) {
3802  $text = false;
3803  break;
3804  }
3805  } elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
3807  $message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
3808  if ( !$message->exists() ) {
3809  $text = false;
3810  break;
3811  }
3812  $content = $message->content();
3813  $text = $message->plain();
3814  } else {
3815  break;
3816  }
3817  if ( !$content ) {
3818  break;
3819  }
3820  # Redirect?
3821  $finalTitle = $title;
3822  $title = $content->getRedirectTarget();
3823  }
3824  return array(
3825  'text' => $text,
3826  'finalTitle' => $finalTitle,
3827  'deps' => $deps );
3828  }
3829 
3837  function fetchFile( $title, $options = array() ) {
3838  $res = $this->fetchFileAndTitle( $title, $options );
3839  return $res[0];
3840  }
3841 
3849  function fetchFileAndTitle( $title, $options = array() ) {
3850  $file = $this->fetchFileNoRegister( $title, $options );
3851 
3852  $time = $file ? $file->getTimestamp() : false;
3853  $sha1 = $file ? $file->getSha1() : false;
3854  # Register the file as a dependency...
3855  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3856  if ( $file && !$title->equals( $file->getTitle() ) ) {
3857  # Update fetched file title
3858  $title = $file->getTitle();
3859  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3860  }
3861  return array( $file, $title );
3862  }
3863 
3874  protected function fetchFileNoRegister( $title, $options = array() ) {
3875  if ( isset( $options['broken'] ) ) {
3876  $file = false; // broken thumbnail forced by hook
3877  } elseif ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
3878  $file = RepoGroup::singleton()->findFileFromKey( $options['sha1'], $options );
3879  } else { // get by (name,timestamp)
3881  }
3882  return $file;
3883  }
3884 
3893  function interwikiTransclude( $title, $action ) {
3894  global $wgEnableScaryTranscluding;
3895 
3896  if ( !$wgEnableScaryTranscluding ) {
3897  return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
3898  }
3899 
3900  $url = $title->getFullURL( array( 'action' => $action ) );
3901 
3902  if ( strlen( $url ) > 255 ) {
3903  return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
3904  }
3905  return $this->fetchScaryTemplateMaybeFromCache( $url );
3906  }
3907 
3912  function fetchScaryTemplateMaybeFromCache( $url ) {
3913  global $wgTranscludeCacheExpiry;
3914  $dbr = wfGetDB( DB_SLAVE );
3915  $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
3916  $obj = $dbr->selectRow( 'transcache', array( 'tc_time', 'tc_contents' ),
3917  array( 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ) );
3918  if ( $obj ) {
3919  return $obj->tc_contents;
3920  }
3921 
3922  $req = MWHttpRequest::factory( $url );
3923  $status = $req->execute(); // Status object
3924  if ( $status->isOK() ) {
3925  $text = $req->getContent();
3926  } elseif ( $req->getStatus() != 200 ) { // Though we failed to fetch the content, this status is useless.
3927  return wfMessage( 'scarytranscludefailed-httpstatus', $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text();
3928  } else {
3929  return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
3930  }
3931 
3932  $dbw = wfGetDB( DB_MASTER );
3933  $dbw->replace( 'transcache', array( 'tc_url' ), array(
3934  'tc_url' => $url,
3935  'tc_time' => $dbw->timestamp( time() ),
3936  'tc_contents' => $text
3937  ) );
3938  return $text;
3939  }
3940 
3950  function argSubstitution( $piece, $frame ) {
3951  wfProfileIn( __METHOD__ );
3952 
3953  $error = false;
3954  $parts = $piece['parts'];
3955  $nameWithSpaces = $frame->expand( $piece['title'] );
3956  $argName = trim( $nameWithSpaces );
3957  $object = false;
3958  $text = $frame->getArgument( $argName );
3959  if ( $text === false && $parts->getLength() > 0
3960  && ( $this->ot['html']
3961  || $this->ot['pre']
3962  || ( $this->ot['wiki'] && $frame->isTemplate() )
3963  )
3964  ) {
3965  # No match in frame, use the supplied default
3966  $object = $parts->item( 0 )->getChildren();
3967  }
3968  if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
3969  $error = '<!-- WARNING: argument omitted, expansion size too large -->';
3970  $this->limitationWarn( 'post-expand-template-argument' );
3971  }
3972 
3973  if ( $text === false && $object === false ) {
3974  # No match anywhere
3975  $object = $frame->virtualBracketedImplode( '{{{', '|', '}}}', $nameWithSpaces, $parts );
3976  }
3977  if ( $error !== false ) {
3978  $text .= $error;
3979  }
3980  if ( $object !== false ) {
3981  $ret = array( 'object' => $object );
3982  } else {
3983  $ret = array( 'text' => $text );
3984  }
3985 
3986  wfProfileOut( __METHOD__ );
3987  return $ret;
3988  }
3989 
4005  function extensionSubstitution( $params, $frame ) {
4006  $name = $frame->expand( $params['name'] );
4007  $attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
4008  $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
4009  $marker = "{$this->mUniqPrefix}-$name-" . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
4010 
4011  $isFunctionTag = isset( $this->mFunctionTagHooks[strtolower( $name )] ) &&
4012  ( $this->ot['html'] || $this->ot['pre'] );
4013  if ( $isFunctionTag ) {
4014  $markerType = 'none';
4015  } else {
4016  $markerType = 'general';
4017  }
4018  if ( $this->ot['html'] || $isFunctionTag ) {
4019  $name = strtolower( $name );
4020  $attributes = Sanitizer::decodeTagAttributes( $attrText );
4021  if ( isset( $params['attributes'] ) ) {
4022  $attributes = $attributes + $params['attributes'];
4023  }
4024 
4025  if ( isset( $this->mTagHooks[$name] ) ) {
4026  # Workaround for PHP bug 35229 and similar
4027  if ( !is_callable( $this->mTagHooks[$name] ) ) {
4028  throw new MWException( "Tag hook for $name is not callable\n" );
4029  }
4030  $output = call_user_func_array( $this->mTagHooks[$name],
4031  array( $content, $attributes, $this, $frame ) );
4032  } elseif ( isset( $this->mFunctionTagHooks[$name] ) ) {
4033  list( $callback, ) = $this->mFunctionTagHooks[$name];
4034  if ( !is_callable( $callback ) ) {
4035  throw new MWException( "Tag hook for $name is not callable\n" );
4036  }
4037 
4038  $output = call_user_func_array( $callback, array( &$this, $frame, $content, $attributes ) );
4039  } else {
4040  $output = '<span class="error">Invalid tag extension name: ' .
4041  htmlspecialchars( $name ) . '</span>';
4042  }
4043 
4044  if ( is_array( $output ) ) {
4045  # Extract flags to local scope (to override $markerType)
4046  $flags = $output;
4047  $output = $flags[0];
4048  unset( $flags[0] );
4049  extract( $flags );
4050  }
4051  } else {
4052  if ( is_null( $attrText ) ) {
4053  $attrText = '';
4054  }
4055  if ( isset( $params['attributes'] ) ) {
4056  foreach ( $params['attributes'] as $attrName => $attrValue ) {
4057  $attrText .= ' ' . htmlspecialchars( $attrName ) . '="' .
4058  htmlspecialchars( $attrValue ) . '"';
4059  }
4060  }
4061  if ( $content === null ) {
4062  $output = "<$name$attrText/>";
4063  } else {
4064  $close = is_null( $params['close'] ) ? '' : $frame->expand( $params['close'] );
4065  $output = "<$name$attrText>$content$close";
4066  }
4067  }
4068 
4069  if ( $markerType === 'none' ) {
4070  return $output;
4071  } elseif ( $markerType === 'nowiki' ) {
4072  $this->mStripState->addNoWiki( $marker, $output );
4073  } elseif ( $markerType === 'general' ) {
4074  $this->mStripState->addGeneral( $marker, $output );
4075  } else {
4076  throw new MWException( __METHOD__ . ': invalid marker type' );
4077  }
4078  return $marker;
4079  }
4080 
4088  function incrementIncludeSize( $type, $size ) {
4089  if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
4090  return false;
4091  } else {
4092  $this->mIncludeSizes[$type] += $size;
4093  return true;
4094  }
4095  }
4096 
4102  function incrementExpensiveFunctionCount() {
4103  $this->mExpensiveFunctionCount++;
4104  return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
4105  }
4106 
4115  function doDoubleUnderscore( $text ) {
4116  wfProfileIn( __METHOD__ );
4117 
4118  # The position of __TOC__ needs to be recorded
4119  $mw = MagicWord::get( 'toc' );
4120  if ( $mw->match( $text ) ) {
4121  $this->mShowToc = true;
4122  $this->mForceTocPosition = true;
4123 
4124  # Set a placeholder. At the end we'll fill it in with the TOC.
4125  $text = $mw->replace( '<!--MWTOC-->', $text, 1 );
4126 
4127  # Only keep the first one.
4128  $text = $mw->replace( '', $text );
4129  }
4130 
4131  # Now match and remove the rest of them
4133  $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
4134 
4135  if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) {
4136  $this->mOutput->mNoGallery = true;
4137  }
4138  if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) {
4139  $this->mShowToc = false;
4140  }
4141  if ( isset( $this->mDoubleUnderscores['hiddencat'] ) && $this->mTitle->getNamespace() == NS_CATEGORY ) {
4142  $this->addTrackingCategory( 'hidden-category-category' );
4143  }
4144  # (bug 8068) Allow control over whether robots index a page.
4145  #
4146  # @todo FIXME: Bug 14899: __INDEX__ always overrides __NOINDEX__ here! This
4147  # is not desirable, the last one on the page should win.
4148  if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->mTitle->canUseNoindex() ) {
4149  $this->mOutput->setIndexPolicy( 'noindex' );
4150  $this->addTrackingCategory( 'noindex-category' );
4151  }
4152  if ( isset( $this->mDoubleUnderscores['index'] ) && $this->mTitle->canUseNoindex() ) {
4153  $this->mOutput->setIndexPolicy( 'index' );
4154  $this->addTrackingCategory( 'index-category' );
4155  }
4156 
4157  # Cache all double underscores in the database
4158  foreach ( $this->mDoubleUnderscores as $key => $val ) {
4159  $this->mOutput->setProperty( $key, '' );
4160  }
4161 
4162  wfProfileOut( __METHOD__ );
4163  return $text;
4164  }
4165 
4177  public function addTrackingCategory( $msg ) {
4178  if ( $this->mTitle->getNamespace() === NS_SPECIAL ) {
4179  wfDebug( __METHOD__ . ": Not adding tracking category $msg to special page!\n" );
4180  return false;
4181  }
4182  // Important to parse with correct title (bug 31469)
4183  $cat = wfMessage( $msg )
4184  ->title( $this->getTitle() )
4185  ->inContentLanguage()
4186  ->text();
4187 
4188  # Allow tracking categories to be disabled by setting them to "-"
4189  if ( $cat === '-' ) {
4190  return false;
4191  }
4192 
4193  $containerCategory = Title::makeTitleSafe( NS_CATEGORY, $cat );
4194  if ( $containerCategory ) {
4195  $this->mOutput->addCategory( $containerCategory->getDBkey(), $this->getDefaultSort() );
4196  return true;
4197  } else {
4198  wfDebug( __METHOD__ . ": [[MediaWiki:$msg]] is not a valid title!\n" );
4199  return false;
4200  }
4201  }
4202 
4219  function formatHeadings( $text, $origText, $isMain = true ) {
4220  global $wgMaxTocLevel, $wgExperimentalHtmlIds;
4221 
4222  # Inhibit editsection links if requested in the page
4223  if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
4224  $maybeShowEditLink = $showEditLink = false;
4225  } else {
4226  $maybeShowEditLink = true; /* Actual presence will depend on ParserOptions option */
4227  $showEditLink = $this->mOptions->getEditSection();
4228  }
4229  if ( $showEditLink ) {
4230  $this->mOutput->setEditSectionTokens( true );
4231  }
4232 
4233  # Get all headlines for numbering them and adding funky stuff like [edit]
4234  # links - this is for later, but we need the number of headlines right now
4235  $matches = array();
4236  $numMatches = preg_match_all( '/<H(?P<level>[1-6])(?P<attrib>.*?' . '>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i', $text, $matches );
4237 
4238  # if there are fewer than 4 headlines in the article, do not show TOC
4239  # unless it's been explicitly enabled.
4240  $enoughToc = $this->mShowToc &&
4241  ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
4242 
4243  # Allow user to stipulate that a page should have a "new section"
4244  # link added via __NEWSECTIONLINK__
4245  if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) {
4246  $this->mOutput->setNewSection( true );
4247  }
4248 
4249  # Allow user to remove the "new section"
4250  # link via __NONEWSECTIONLINK__
4251  if ( isset( $this->mDoubleUnderscores['nonewsectionlink'] ) ) {
4252  $this->mOutput->hideNewSection( true );
4253  }
4254 
4255  # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
4256  # override above conditions and always show TOC above first header
4257  if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) {
4258  $this->mShowToc = true;
4259  $enoughToc = true;
4260  }
4261 
4262  # headline counter
4263  $headlineCount = 0;
4264  $numVisible = 0;
4265 
4266  # Ugh .. the TOC should have neat indentation levels which can be
4267  # passed to the skin functions. These are determined here
4268  $toc = '';
4269  $full = '';
4270  $head = array();
4271  $sublevelCount = array();
4272  $levelCount = array();
4273  $level = 0;
4274  $prevlevel = 0;
4275  $toclevel = 0;
4276  $prevtoclevel = 0;
4277  $markerRegex = "{$this->mUniqPrefix}-h-(\d+)-" . self::MARKER_SUFFIX;
4278  $baseTitleText = $this->mTitle->getPrefixedDBkey();
4279  $oldType = $this->mOutputType;
4280  $this->setOutputType( self::OT_WIKI );
4281  $frame = $this->getPreprocessor()->newFrame();
4282  $root = $this->preprocessToDom( $origText );
4283  $node = $root->getFirstChild();
4284  $byteOffset = 0;
4285  $tocraw = array();
4286  $refers = array();
4287 
4288  foreach ( $matches[3] as $headline ) {
4289  $isTemplate = false;
4290  $titleText = false;
4291  $sectionIndex = false;
4292  $numbering = '';
4293  $markerMatches = array();
4294  if ( preg_match( "/^$markerRegex/", $headline, $markerMatches ) ) {
4295  $serial = $markerMatches[1];
4296  list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
4297  $isTemplate = ( $titleText != $baseTitleText );
4298  $headline = preg_replace( "/^$markerRegex\\s*/", "", $headline );
4299  }
4300 
4301  if ( $toclevel ) {
4302  $prevlevel = $level;
4303  }
4304  $level = $matches[1][$headlineCount];
4305 
4306  if ( $level > $prevlevel ) {
4307  # Increase TOC level
4308  $toclevel++;
4309  $sublevelCount[$toclevel] = 0;
4310  if ( $toclevel < $wgMaxTocLevel ) {
4311  $prevtoclevel = $toclevel;
4312  $toc .= Linker::tocIndent();
4313  $numVisible++;
4314  }
4315  } elseif ( $level < $prevlevel && $toclevel > 1 ) {
4316  # Decrease TOC level, find level to jump to
4317 
4318  for ( $i = $toclevel; $i > 0; $i-- ) {
4319  if ( $levelCount[$i] == $level ) {
4320  # Found last matching level
4321  $toclevel = $i;
4322  break;
4323  } elseif ( $levelCount[$i] < $level ) {
4324  # Found first matching level below current level
4325  $toclevel = $i + 1;
4326  break;
4327  }
4328  }
4329  if ( $i == 0 ) {
4330  $toclevel = 1;
4331  }
4332  if ( $toclevel < $wgMaxTocLevel ) {
4333  if ( $prevtoclevel < $wgMaxTocLevel ) {
4334  # Unindent only if the previous toc level was shown :p
4335  $toc .= Linker::tocUnindent( $prevtoclevel - $toclevel );
4336  $prevtoclevel = $toclevel;
4337  } else {
4338  $toc .= Linker::tocLineEnd();
4339  }
4340  }
4341  } else {
4342  # No change in level, end TOC line
4343  if ( $toclevel < $wgMaxTocLevel ) {
4344  $toc .= Linker::tocLineEnd();
4345  }
4346  }
4347 
4348  $levelCount[$toclevel] = $level;
4349 
4350  # count number of headlines for each level
4351  $sublevelCount[$toclevel]++;
4352  $dot = 0;
4353  for ( $i = 1; $i <= $toclevel; $i++ ) {
4354  if ( !empty( $sublevelCount[$i] ) ) {
4355  if ( $dot ) {
4356  $numbering .= '.';
4357  }
4358  $numbering .= $this->getTargetLanguage()->formatNum( $sublevelCount[$i] );
4359  $dot = 1;
4360  }
4361  }
4362 
4363  # The safe header is a version of the header text safe to use for links
4364 
4365  # Remove link placeholders by the link text.
4366  # <!--LINK number-->
4367  # turns into
4368  # link text with suffix
4369  # Do this before unstrip since link text can contain strip markers
4370  $safeHeadline = $this->replaceLinkHoldersText( $headline );
4371 
4372  # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4373  $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4374 
4375  # Strip out HTML (first regex removes any tag not allowed)
4376  # Allowed tags are:
4377  # * <sup> and <sub> (bug 8393)
4378  # * <i> (bug 26375)
4379  # * <b> (r105284)
4380  # * <span dir="rtl"> and <span dir="ltr"> (bug 35167)
4381  #
4382  # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4383  # to allow setting directionality in toc items.
4384  $tocline = preg_replace(
4385  array( '#<(?!/?(span|sup|sub|i|b)(?: [^>]*)?>).*?' . '>#', '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|i|b))(?: .*?)?' . '>#' ),
4386  array( '', '<$1>' ),
4387  $safeHeadline
4388  );
4389  $tocline = trim( $tocline );
4390 
4391  # For the anchor, strip out HTML-y stuff period
4392  $safeHeadline = preg_replace( '/<.*?' . '>/', '', $safeHeadline );
4393  $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4394 
4395  # Save headline for section edit hint before it's escaped
4396  $headlineHint = $safeHeadline;
4397 
4398  if ( $wgExperimentalHtmlIds ) {
4399  # For reverse compatibility, provide an id that's
4400  # HTML4-compatible, like we used to.
4401  #
4402  # It may be worth noting, academically, that it's possible for
4403  # the legacy anchor to conflict with a non-legacy headline
4404  # anchor on the page. In this case likely the "correct" thing
4405  # would be to either drop the legacy anchors or make sure
4406  # they're numbered first. However, this would require people
4407  # to type in section names like "abc_.D7.93.D7.90.D7.A4"
4408  # manually, so let's not bother worrying about it.
4409  $legacyHeadline = Sanitizer::escapeId( $safeHeadline,
4410  array( 'noninitial', 'legacy' ) );
4411  $safeHeadline = Sanitizer::escapeId( $safeHeadline );
4412 
4413  if ( $legacyHeadline == $safeHeadline ) {
4414  # No reason to have both (in fact, we can't)
4415  $legacyHeadline = false;
4416  }
4417  } else {
4418  $legacyHeadline = false;
4419  $safeHeadline = Sanitizer::escapeId( $safeHeadline,
4420  'noninitial' );
4421  }
4422 
4423  # HTML names must be case-insensitively unique (bug 10721).
4424  # This does not apply to Unicode characters per
4425  # http://dev.w3.org/html5/spec/infrastructure.html#case-sensitivity-and-string-comparison
4426  # @todo FIXME: We may be changing them depending on the current locale.
4427  $arrayKey = strtolower( $safeHeadline );
4428  if ( $legacyHeadline === false ) {
4429  $legacyArrayKey = false;
4430  } else {
4431  $legacyArrayKey = strtolower( $legacyHeadline );
4432  }
4433 
4434  # count how many in assoc. array so we can track dupes in anchors
4435  if ( isset( $refers[$arrayKey] ) ) {
4436  $refers[$arrayKey]++;
4437  } else {
4438  $refers[$arrayKey] = 1;
4439  }
4440  if ( isset( $refers[$legacyArrayKey] ) ) {
4441  $refers[$legacyArrayKey]++;
4442  } else {
4443  $refers[$legacyArrayKey] = 1;
4444  }
4445 
4446  # Don't number the heading if it is the only one (looks silly)
4447  if ( count( $matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) {
4448  # the two are different if the line contains a link
4449  $headline = Html::element( 'span', array( 'class' => 'mw-headline-number' ), $numbering ) . ' ' . $headline;
4450  }
4451 
4452  # Create the anchor for linking from the TOC to the section
4453  $anchor = $safeHeadline;
4454  $legacyAnchor = $legacyHeadline;
4455  if ( $refers[$arrayKey] > 1 ) {
4456  $anchor .= '_' . $refers[$arrayKey];
4457  }
4458  if ( $legacyHeadline !== false && $refers[$legacyArrayKey] > 1 ) {
4459  $legacyAnchor .= '_' . $refers[$legacyArrayKey];
4460  }
4461  if ( $enoughToc && ( !isset( $wgMaxTocLevel ) || $toclevel < $wgMaxTocLevel ) ) {
4462  $toc .= Linker::tocLine( $anchor, $tocline,
4463  $numbering, $toclevel, ( $isTemplate ? false : $sectionIndex ) );
4464  }
4465 
4466  # Add the section to the section tree
4467  # Find the DOM node for this header
4468  $noOffset = ( $isTemplate || $sectionIndex === false );
4469  while ( $node && !$noOffset ) {
4470  if ( $node->getName() === 'h' ) {
4471  $bits = $node->splitHeading();
4472  if ( $bits['i'] == $sectionIndex ) {
4473  break;
4474  }
4475  }
4476  $byteOffset += mb_strlen( $this->mStripState->unstripBoth(
4477  $frame->expand( $node, PPFrame::RECOVER_ORIG ) ) );
4478  $node = $node->getNextSibling();
4479  }
4480  $tocraw[] = array(
4481  'toclevel' => $toclevel,
4482  'level' => $level,
4483  'line' => $tocline,
4484  'number' => $numbering,
4485  'index' => ( $isTemplate ? 'T-' : '' ) . $sectionIndex,
4486  'fromtitle' => $titleText,
4487  'byteoffset' => ( $noOffset ? null : $byteOffset ),
4488  'anchor' => $anchor,
4489  );
4490 
4491  # give headline the correct <h#> tag
4492  if ( $maybeShowEditLink && $sectionIndex !== false ) {
4493  // Output edit section links as markers with styles that can be customized by skins
4494  if ( $isTemplate ) {
4495  # Put a T flag in the section identifier, to indicate to extractSections()
4496  # that sections inside <includeonly> should be counted.
4497  $editlinkArgs = array( $titleText, "T-$sectionIndex"/*, null */ );
4498  } else {
4499  $editlinkArgs = array( $this->mTitle->getPrefixedText(), $sectionIndex, $headlineHint );
4500  }
4501  // We use a bit of pesudo-xml for editsection markers. The language converter is run later on
4502  // Using a UNIQ style marker leads to the converter screwing up the tokens when it converts stuff
4503  // And trying to insert strip tags fails too. At this point all real inputted tags have already been escaped
4504  // so we don't have to worry about a user trying to input one of these markers directly.
4505  // We use a page and section attribute to stop the language converter from converting these important bits
4506  // of data, but put the headline hint inside a content block because the language converter is supposed to
4507  // be able to convert that piece of data.
4508  $editlink = '<mw:editsection page="' . htmlspecialchars( $editlinkArgs[0] );
4509  $editlink .= '" section="' . htmlspecialchars( $editlinkArgs[1] ) . '"';
4510  if ( isset( $editlinkArgs[2] ) ) {
4511  $editlink .= '>' . $editlinkArgs[2] . '</mw:editsection>';
4512  } else {
4513  $editlink .= '/>';
4514  }
4515  } else {
4516  $editlink = '';
4517  }
4518  $head[$headlineCount] = Linker::makeHeadline( $level,
4519  $matches['attrib'][$headlineCount], $anchor, $headline,
4520  $editlink, $legacyAnchor );
4521 
4522  $headlineCount++;
4523  }
4524 
4525  $this->setOutputType( $oldType );
4526 
4527  # Never ever show TOC if no headers
4528  if ( $numVisible < 1 ) {
4529  $enoughToc = false;
4530  }
4531 
4532  if ( $enoughToc ) {
4533  if ( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
4534  $toc .= Linker::tocUnindent( $prevtoclevel - 1 );
4535  }
4536  $toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
4537  $this->mOutput->setTOCHTML( $toc );
4538  $toc = self::TOC_START . $toc . self::TOC_END;
4539  }
4540 
4541  if ( $isMain ) {
4542  $this->mOutput->setSections( $tocraw );
4543  }
4544 
4545  # split up and insert constructed headlines
4546  $blocks = preg_split( '/<H[1-6].*?' . '>[\s\S]*?<\/H[1-6]>/i', $text );
4547  $i = 0;
4548 
4549  // build an array of document sections
4550  $sections = array();
4551  foreach ( $blocks as $block ) {
4552  // $head is zero-based, sections aren't.
4553  if ( empty( $head[$i - 1] ) ) {
4554  $sections[$i] = $block;
4555  } else {
4556  $sections[$i] = $head[$i - 1] . $block;
4557  }
4558 
4569  wfRunHooks( 'ParserSectionCreate', array( $this, $i, &$sections[$i], $showEditLink ) );
4570 
4571  $i++;
4572  }
4573 
4574  if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
4575  // append the TOC at the beginning
4576  // Top anchor now in skin
4577  $sections[0] = $sections[0] . $toc . "\n";
4578  }
4579 
4580  $full .= join( '', $sections );
4581 
4582  if ( $this->mForceTocPosition ) {
4583  return str_replace( '<!--MWTOC-->', $toc, $full );
4584  } else {
4585  return $full;
4586  }
4587  }
4588 
4600  public function preSaveTransform( $text, Title $title, User $user, ParserOptions $options, $clearState = true ) {
4601  $this->startParse( $title, $options, self::OT_WIKI, $clearState );
4602  $this->setUser( $user );
4603 
4604  $pairs = array(
4605  "\r\n" => "\n",
4606  );
4607  $text = str_replace( array_keys( $pairs ), array_values( $pairs ), $text );
4608  if ( $options->getPreSaveTransform() ) {
4609  $text = $this->pstPass2( $text, $user );
4610  }
4611  $text = $this->mStripState->unstripBoth( $text );
4612 
4613  $this->setUser( null ); #Reset
4614 
4615  return $text;
4616  }
4617 
4626  private function pstPass2( $text, $user ) {
4628 
4629  # Note: This is the timestamp saved as hardcoded wikitext to
4630  # the database, we use $wgContLang here in order to give
4631  # everyone the same signature and use the default one rather
4632  # than the one selected in each user's preferences.
4633  # (see also bug 12815)
4634  $ts = $this->mOptions->getTimestamp();
4636  $ts = $timestamp->format( 'YmdHis' );
4637  $tzMsg = $timestamp->format( 'T' ); # might vary on DST changeover!
4638 
4639  # Allow translation of timezones through wiki. format() can return
4640  # whatever crap the system uses, localised or not, so we cannot
4641  # ship premade translations.
4642  $key = 'timezone-' . strtolower( trim( $tzMsg ) );
4643  $msg = wfMessage( $key )->inContentLanguage();
4644  if ( $msg->exists() ) {
4645  $tzMsg = $msg->text();
4646  }
4647 
4648  $d = $wgContLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4649 
4650  # Variable replacement
4651  # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4652  $text = $this->replaceVariables( $text );
4653 
4654  # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4655  # which may corrupt this parser instance via its wfMessage()->text() call-
4656 
4657  # Signatures
4658  $sigText = $this->getUserSig( $user );
4659  $text = strtr( $text, array(
4660  '~~~~~' => $d,
4661  '~~~~' => "$sigText $d",
4662  '~~~' => $sigText
4663  ) );
4664 
4665  # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4666  $tc = '[' . Title::legalChars() . ']';
4667  $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4668 
4669  $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/"; # [[ns:page (context)|]]
4670  $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/"; # [[ns:page(context)|]] (double-width brackets, added in r40257)
4671  $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/"; # [[ns:page (context), context|]] (using either single or double-width comma)
4672  $p2 = "/\[\[\\|($tc+)]]/"; # [[|page]] (reverse pipe trick: add context from page title)
4673 
4674  # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4675  $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
4676  $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]', $text );
4677  $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
4678 
4679  $t = $this->mTitle->getText();
4680  $m = array();
4681  if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4682  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4683  } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4684  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4685  } else {
4686  # if there's no context, don't bother duplicating the title
4687  $text = preg_replace( $p2, '[[\\1]]', $text );
4688  }
4689 
4690  # Trim trailing whitespace
4691  $text = rtrim( $text );
4692 
4693  return $text;
4694  }
4695 
4710  function getUserSig( &$user, $nickname = false, $fancySig = null ) {
4711  global $wgMaxSigChars;
4712 
4713  $username = $user->getName();
4714 
4715  # If not given, retrieve from the user object.
4716  if ( $nickname === false ) {
4717  $nickname = $user->getOption( 'nickname' );
4718  }
4719 
4720  if ( is_null( $fancySig ) ) {
4721  $fancySig = $user->getBoolOption( 'fancysig' );
4722  }
4723 
4724  $nickname = $nickname == null ? $username : $nickname;
4725 
4726  if ( mb_strlen( $nickname ) > $wgMaxSigChars ) {
4727  $nickname = $username;
4728  wfDebug( __METHOD__ . ": $username has overlong signature.\n" );
4729  } elseif ( $fancySig !== false ) {
4730  # Sig. might contain markup; validate this
4731  if ( $this->validateSig( $nickname ) !== false ) {
4732  # Validated; clean up (if needed) and return it
4733  return $this->cleanSig( $nickname, true );
4734  } else {
4735  # Failed to validate; fall back to the default
4736  $nickname = $username;
4737  wfDebug( __METHOD__ . ": $username has bad XML tags in signature.\n" );
4738  }
4739  }
4740 
4741  # Make sure nickname doesnt get a sig in a sig
4742  $nickname = self::cleanSigInSig( $nickname );
4743 
4744  # If we're still here, make it a link to the user page
4745  $userText = wfEscapeWikiText( $username );
4746  $nickText = wfEscapeWikiText( $nickname );
4747  $msgName = $user->isAnon() ? 'signature-anon' : 'signature';
4748 
4749  return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()->title( $this->getTitle() )->text();
4750  }
4751 
4758  function validateSig( $text ) {
4759  return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
4760  }
4761 
4772  public function cleanSig( $text, $parsing = false ) {
4773  if ( !$parsing ) {
4774  global $wgTitle;
4775  $this->startParse( $wgTitle, new ParserOptions, self::OT_PREPROCESS, true );
4776  }
4777 
4778  # Option to disable this feature
4779  if ( !$this->mOptions->getCleanSignatures() ) {
4780  return $text;
4781  }
4782 
4783  # @todo FIXME: Regex doesn't respect extension tags or nowiki
4784  # => Move this logic to braceSubstitution()
4785  $substWord = MagicWord::get( 'subst' );
4786  $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
4787  $substText = '{{' . $substWord->getSynonym( 0 );
4788 
4789  $text = preg_replace( $substRegex, $substText, $text );
4790  $text = self::cleanSigInSig( $text );
4791  $dom = $this->preprocessToDom( $text );
4792  $frame = $this->getPreprocessor()->newFrame();
4793  $text = $frame->expand( $dom );
4794 
4795  if ( !$parsing ) {
4796  $text = $this->mStripState->unstripBoth( $text );
4797  }
4798 
4799  return $text;
4800  }
4801 
4808  public static function cleanSigInSig( $text ) {
4809  $text = preg_replace( '/~{3,5}/', '', $text );
4810  return $text;
4811  }
4812 
4822  public function startExternalParse( Title $title = null, ParserOptions $options, $outputType, $clearState = true ) {
4823  $this->startParse( $title, $options, $outputType, $clearState );
4824  }
4825 
4832  private function startParse( Title $title = null, ParserOptions $options, $outputType, $clearState = true ) {
4833  $this->setTitle( $title );
4834  $this->mOptions = $options;
4835  $this->setOutputType( $outputType );
4836  if ( $clearState ) {
4837  $this->clearState();
4838  }
4839  }
4840 
4849  public function transformMsg( $text, $options, $title = null ) {
4850  static $executing = false;
4851 
4852  # Guard against infinite recursion
4853  if ( $executing ) {
4854  return $text;
4855  }
4856  $executing = true;
4857 
4858  wfProfileIn( __METHOD__ );
4859  if ( !$title ) {
4860  global $wgTitle;
4861  $title = $wgTitle;
4862  }
4863 
4864  $text = $this->preprocess( $text, $title, $options );
4865 
4866  $executing = false;
4867  wfProfileOut( __METHOD__ );
4868  return $text;
4869  }
4870 
4895  public function setHook( $tag, $callback ) {
4896  $tag = strtolower( $tag );
4897  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4898  throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
4899  }
4900  $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null;
4901  $this->mTagHooks[$tag] = $callback;
4902  if ( !in_array( $tag, $this->mStripList ) ) {
4903  $this->mStripList[] = $tag;
4904  }
4905 
4906  return $oldVal;
4907  }
4908 
4926  function setTransparentTagHook( $tag, $callback ) {
4927  $tag = strtolower( $tag );
4928  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4929  throw new MWException( "Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
4930  }
4931  $oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] : null;
4932  $this->mTransparentTagHooks[$tag] = $callback;
4933 
4934  return $oldVal;
4935  }
4936 
4940  function clearTagHooks() {
4941  $this->mTagHooks = array();
4942  $this->mFunctionTagHooks = array();
4943  $this->mStripList = $this->mDefaultStripList;
4944  }
4945 
4989  public function setFunctionHook( $id, $callback, $flags = 0 ) {
4991 
4992  $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] : null;
4993  $this->mFunctionHooks[$id] = array( $callback, $flags );
4994 
4995  # Add to function cache
4996  $mw = MagicWord::get( $id );
4997  if ( !$mw ) {
4998  throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
4999  }
5000 
5001  $synonyms = $mw->getSynonyms();
5002  $sensitive = intval( $mw->isCaseSensitive() );
5003 
5004  foreach ( $synonyms as $syn ) {
5005  # Case
5006  if ( !$sensitive ) {
5007  $syn = $wgContLang->lc( $syn );
5008  }
5009  # Add leading hash
5010  if ( !( $flags & SFH_NO_HASH ) ) {
5011  $syn = '#' . $syn;
5012  }
5013  # Remove trailing colon
5014  if ( substr( $syn, -1, 1 ) === ':' ) {
5015  $syn = substr( $syn, 0, -1 );
5016  }
5017  $this->mFunctionSynonyms[$sensitive][$syn] = $id;
5018  }
5019  return $oldVal;
5020  }
5021 
5027  function getFunctionHooks() {
5028  return array_keys( $this->mFunctionHooks );
5029  }
5030 
5041  function setFunctionTagHook( $tag, $callback, $flags ) {
5042  $tag = strtolower( $tag );
5043  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
5044  throw new MWException( "Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
5045  }
5046  $old = isset( $this->mFunctionTagHooks[$tag] ) ?
5047  $this->mFunctionTagHooks[$tag] : null;
5048  $this->mFunctionTagHooks[$tag] = array( $callback, $flags );
5049 
5050  if ( !in_array( $tag, $this->mStripList ) ) {
5051  $this->mStripList[] = $tag;
5052  }
5053 
5054  return $old;
5055  }
5056 
5067  function replaceLinkHolders( &$text, $options = 0 ) {
5068  return $this->mLinkHolders->replace( $text );
5069  }
5070 
5078  function replaceLinkHoldersText( $text ) {
5079  return $this->mLinkHolders->replaceText( $text );
5080  }
5081 
5095  function renderImageGallery( $text, $params ) {
5096  wfProfileIn( __METHOD__ );
5097 
5098  $mode = false;
5099  if ( isset( $params['mode'] ) ) {
5100  $mode = $params['mode'];
5101  }
5102 
5103  try {
5104  $ig = ImageGalleryBase::factory( $mode );
5105  } catch ( MWException $e ) {
5106  // If invalid type set, fallback to default.
5107  $ig = ImageGalleryBase::factory( false );
5108  }
5109 
5110  $ig->setContextTitle( $this->mTitle );
5111  $ig->setShowBytes( false );
5112  $ig->setShowFilename( false );
5113  $ig->setParser( $this );
5114  $ig->setHideBadImages();
5115  $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'table' ) );
5116 
5117  if ( isset( $params['showfilename'] ) ) {
5118  $ig->setShowFilename( true );
5119  } else {
5120  $ig->setShowFilename( false );
5121  }
5122  if ( isset( $params['caption'] ) ) {
5123  $caption = $params['caption'];
5124  $caption = htmlspecialchars( $caption );
5125  $caption = $this->replaceInternalLinks( $caption );
5126  $ig->setCaptionHtml( $caption );
5127  }
5128  if ( isset( $params['perrow'] ) ) {
5129  $ig->setPerRow( $params['perrow'] );
5130  }
5131  if ( isset( $params['widths'] ) ) {
5132  $ig->setWidths( $params['widths'] );
5133  }
5134  if ( isset( $params['heights'] ) ) {
5135  $ig->setHeights( $params['heights'] );
5136  }
5137  $ig->setAdditionalOptions( $params );
5138 
5139  wfRunHooks( 'BeforeParserrenderImageGallery', array( &$this, &$ig ) );
5140 
5141  $lines = StringUtils::explode( "\n", $text );
5142  foreach ( $lines as $line ) {
5143  # match lines like these:
5144  # Image:someimage.jpg|This is some image
5145  $matches = array();
5146  preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
5147  # Skip empty lines
5148  if ( count( $matches ) == 0 ) {
5149  continue;
5150  }
5151 
5152  if ( strpos( $matches[0], '%' ) !== false ) {
5153  $matches[1] = rawurldecode( $matches[1] );
5154  }
5156  if ( is_null( $title ) ) {
5157  # Bogus title. Ignore these so we don't bomb out later.
5158  continue;
5159  }
5160 
5161  # We need to get what handler the file uses, to figure out parameters.
5162  # Note, a hook can overide the file name, and chose an entirely different
5163  # file (which potentially could be of a different type and have different handler).
5164  $options = array();
5165  $descQuery = false;
5166  wfRunHooks( 'BeforeParserFetchFileAndTitle',
5167  array( $this, $title, &$options, &$descQuery ) );
5168  # Don't register it now, as ImageGallery does that later.
5169  $file = $this->fetchFileNoRegister( $title, $options );
5170  $handler = $file ? $file->getHandler() : false;
5171 
5172  wfProfileIn( __METHOD__ . '-getMagicWord' );
5173  $paramMap = array(
5174  'img_alt' => 'gallery-internal-alt',
5175  'img_link' => 'gallery-internal-link',
5176  );
5177  if ( $handler ) {
5178  $paramMap = $paramMap + $handler->getParamMap();
5179  // We don't want people to specify per-image widths.
5180  // Additionally the width parameter would need special casing anyhow.
5181  unset( $paramMap['img_width'] );
5182  }
5183 
5184  $mwArray = new MagicWordArray( array_keys( $paramMap ) );
5185  wfProfileOut( __METHOD__ . '-getMagicWord' );
5186 
5187  $label = '';
5188  $alt = '';
5189  $link = '';
5190  $handlerOptions = array();
5191  if ( isset( $matches[3] ) ) {
5192  // look for an |alt= definition while trying not to break existing
5193  // captions with multiple pipes (|) in it, until a more sensible grammar
5194  // is defined for images in galleries
5195 
5196  // FIXME: Doing recursiveTagParse at this stage, and the trim before
5197  // splitting on '|' is a bit odd, and different from makeImage.
5198  $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
5199  $parameterMatches = StringUtils::explode( '|', $matches[3] );
5200 
5201  foreach ( $parameterMatches as $parameterMatch ) {
5202  list( $magicName, $match ) = $mwArray->matchVariableStartToEnd( $parameterMatch );
5203  if ( $magicName ) {
5204  $paramName = $paramMap[$magicName];
5205 
5206  switch ( $paramName ) {
5207  case 'gallery-internal-alt':
5208  $alt = $this->stripAltText( $match, false );
5209  break;
5210  case 'gallery-internal-link':
5211  $linkValue = strip_tags( $this->replaceLinkHoldersText( $match ) );
5212  $chars = self::EXT_LINK_URL_CLASS;
5213  $prots = $this->mUrlProtocols;
5214  //check to see if link matches an absolute url, if not then it must be a wiki link.
5215  if ( preg_match( "/^($prots)$chars+$/u", $linkValue ) ) {
5216  $link = $linkValue;
5217  } else {
5218  $localLinkTitle = Title::newFromText( $linkValue );
5219  if ( $localLinkTitle !== null ) {
5220  $link = $localLinkTitle->getLocalURL();
5221  }
5222  }
5223  break;
5224  default:
5225  // Must be a handler specific parameter.
5226  if ( $handler->validateParam( $paramName, $match ) ) {
5227  $handlerOptions[$paramName] = $match;
5228  } else {
5229  // Guess not. Append it to the caption.
5230  wfDebug( "$parameterMatch failed parameter validation\n" );
5231  $label .= '|' . $parameterMatch;
5232  }
5233  }
5234 
5235  } else {
5236  // concatenate all other pipes
5237  $label .= '|' . $parameterMatch;
5238  }
5239  }
5240  // remove the first pipe
5241  $label = substr( $label, 1 );
5242  }
5243 
5244  $ig->add( $title, $label, $alt, $link, $handlerOptions );
5245  }
5246  $html = $ig->toHTML();
5247  wfProfileOut( __METHOD__ );
5248  return $html;
5249  }
5250 
5255  function getImageParams( $handler ) {
5256  if ( $handler ) {
5257  $handlerClass = get_class( $handler );
5258  } else {
5259  $handlerClass = '';
5260  }
5261  if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5262  # Initialise static lists
5263  static $internalParamNames = array(
5264  'horizAlign' => array( 'left', 'right', 'center', 'none' ),
5265  'vertAlign' => array( 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
5266  'bottom', 'text-bottom' ),
5267  'frame' => array( 'thumbnail', 'manualthumb', 'framed', 'frameless',
5268  'upright', 'border', 'link', 'alt', 'class' ),
5269  );
5270  static $internalParamMap;
5271  if ( !$internalParamMap ) {
5272  $internalParamMap = array();
5273  foreach ( $internalParamNames as $type => $names ) {
5274  foreach ( $names as $name ) {
5275  $magicName = str_replace( '-', '_', "img_$name" );
5276  $internalParamMap[$magicName] = array( $type, $name );
5277  }
5278  }
5279  }
5280 
5281  # Add handler params
5282  $paramMap = $internalParamMap;
5283  if ( $handler ) {
5284  $handlerParamMap = $handler->getParamMap();
5285  foreach ( $handlerParamMap as $magic => $paramName ) {
5286  $paramMap[$magic] = array( 'handler', $paramName );
5287  }
5288  }
5289  $this->mImageParams[$handlerClass] = $paramMap;
5290  $this->mImageParamsMagicArray[$handlerClass] = new MagicWordArray( array_keys( $paramMap ) );
5291  }
5292  return array( $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] );
5293  }
5294 
5303  function makeImage( $title, $options, $holders = false ) {
5304  # Check if the options text is of the form "options|alt text"
5305  # Options are:
5306  # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5307  # * left no resizing, just left align. label is used for alt= only
5308  # * right same, but right aligned
5309  # * none same, but not aligned
5310  # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5311  # * center center the image
5312  # * frame Keep original image size, no magnify-button.
5313  # * framed Same as "frame"
5314  # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5315  # * upright reduce width for upright images, rounded to full __0 px
5316  # * border draw a 1px border around the image
5317  # * alt Text for HTML alt attribute (defaults to empty)
5318  # * class Set a class for img node
5319  # * link Set the target of the image link. Can be external, interwiki, or local
5320  # vertical-align values (no % or length right now):
5321  # * baseline
5322  # * sub
5323  # * super
5324  # * top
5325  # * text-top
5326  # * middle
5327  # * bottom
5328  # * text-bottom
5329 
5330  $parts = StringUtils::explode( "|", $options );
5331 
5332  # Give extensions a chance to select the file revision for us
5333  $options = array();
5334  $descQuery = false;
5335  wfRunHooks( 'BeforeParserFetchFileAndTitle',
5336  array( $this, $title, &$options, &$descQuery ) );
5337  # Fetch and register the file (file title may be different via hooks)
5338  list( $file, $title ) = $this->fetchFileAndTitle( $title, $options );
5339 
5340  # Get parameter map
5341  $handler = $file ? $file->getHandler() : false;
5342 
5343  list( $paramMap, $mwArray ) = $this->getImageParams( $handler );
5344 
5345  if ( !$file ) {
5346  $this->addTrackingCategory( 'broken-file-category' );
5347  }
5348 
5349  # Process the input parameters
5350  $caption = '';
5351  $params = array( 'frame' => array(), 'handler' => array(),
5352  'horizAlign' => array(), 'vertAlign' => array() );
5353  foreach ( $parts as $part ) {
5354  $part = trim( $part );
5355  list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part );
5356  $validated = false;
5357  if ( isset( $paramMap[$magicName] ) ) {
5358  list( $type, $paramName ) = $paramMap[$magicName];
5359 
5360  # Special case; width and height come in one variable together
5361  if ( $type === 'handler' && $paramName === 'width' ) {
5362  $parsedWidthParam = $this->parseWidthParam( $value );
5363  if ( isset( $parsedWidthParam['width'] ) ) {
5364  $width = $parsedWidthParam['width'];
5365  if ( $handler->validateParam( 'width', $width ) ) {
5366  $params[$type]['width'] = $width;
5367  $validated = true;
5368  }
5369  }
5370  if ( isset( $parsedWidthParam['height'] ) ) {
5371  $height = $parsedWidthParam['height'];
5372  if ( $handler->validateParam( 'height', $height ) ) {
5373  $params[$type]['height'] = $height;
5374  $validated = true;
5375  }
5376  }
5377  # else no validation -- bug 13436
5378  } else {
5379  if ( $type === 'handler' ) {
5380  # Validate handler parameter
5381  $validated = $handler->validateParam( $paramName, $value );
5382  } else {
5383  # Validate internal parameters
5384  switch ( $paramName ) {
5385  case 'manualthumb':
5386  case 'alt':
5387  case 'class':
5388  # @todo FIXME: Possibly check validity here for
5389  # manualthumb? downstream behavior seems odd with
5390  # missing manual thumbs.
5391  $validated = true;
5392  $value = $this->stripAltText( $value, $holders );
5393  break;
5394  case 'link':
5395  $chars = self::EXT_LINK_URL_CLASS;
5396  $prots = $this->mUrlProtocols;
5397  if ( $value === '' ) {
5398  $paramName = 'no-link';
5399  $value = true;
5400  $validated = true;
5401  } elseif ( preg_match( "/^(?i)$prots/", $value ) ) {
5402  if ( preg_match( "/^((?i)$prots)$chars+$/u", $value, $m ) ) {
5403  $paramName = 'link-url';
5404  $this->mOutput->addExternalLink( $value );
5405  if ( $this->mOptions->getExternalLinkTarget() ) {
5406  $params[$type]['link-target'] = $this->mOptions->getExternalLinkTarget();
5407  }
5408  $validated = true;
5409  }
5410  } else {
5411  $linkTitle = Title::newFromText( $value );
5412  if ( $linkTitle ) {
5413  $paramName = 'link-title';
5414  $value = $linkTitle;
5415  $this->mOutput->addLink( $linkTitle );
5416  $validated = true;
5417  }
5418  }
5419  break;
5420  default:
5421  # Most other things appear to be empty or numeric...
5422  $validated = ( $value === false || is_numeric( trim( $value ) ) );
5423  }
5424  }
5425 
5426  if ( $validated ) {
5427  $params[$type][$paramName] = $value;
5428  }
5429  }
5430  }
5431  if ( !$validated ) {
5432  $caption = $part;
5433  }
5434  }
5435 
5436  # Process alignment parameters
5437  if ( $params['horizAlign'] ) {
5438  $params['frame']['align'] = key( $params['horizAlign'] );
5439  }
5440  if ( $params['vertAlign'] ) {
5441  $params['frame']['valign'] = key( $params['vertAlign'] );
5442  }
5443 
5444  $params['frame']['caption'] = $caption;
5445 
5446  # Will the image be presented in a frame, with the caption below?
5447  $imageIsFramed = isset( $params['frame']['frame'] )
5448  || isset( $params['frame']['framed'] )
5449  || isset( $params['frame']['thumbnail'] )
5450  || isset( $params['frame']['manualthumb'] );
5451 
5452  # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5453  # came to also set the caption, ordinary text after the image -- which
5454  # makes no sense, because that just repeats the text multiple times in
5455  # screen readers. It *also* came to set the title attribute.
5456  #
5457  # Now that we have an alt attribute, we should not set the alt text to
5458  # equal the caption: that's worse than useless, it just repeats the
5459  # text. This is the framed/thumbnail case. If there's no caption, we
5460  # use the unnamed parameter for alt text as well, just for the time be-
5461  # ing, if the unnamed param is set and the alt param is not.
5462  #
5463  # For the future, we need to figure out if we want to tweak this more,
5464  # e.g., introducing a title= parameter for the title; ignoring the un-
5465  # named parameter entirely for images without a caption; adding an ex-
5466  # plicit caption= parameter and preserving the old magic unnamed para-
5467  # meter for BC; ...
5468  if ( $imageIsFramed ) { # Framed image
5469  if ( $caption === '' && !isset( $params['frame']['alt'] ) ) {
5470  # No caption or alt text, add the filename as the alt text so
5471  # that screen readers at least get some description of the image
5472  $params['frame']['alt'] = $title->getText();
5473  }
5474  # Do not set $params['frame']['title'] because tooltips don't make sense
5475  # for framed images
5476  } else { # Inline image
5477  if ( !isset( $params['frame']['alt'] ) ) {
5478  # No alt text, use the "caption" for the alt text
5479  if ( $caption !== '' ) {
5480  $params['frame']['alt'] = $this->stripAltText( $caption, $holders );
5481  } else {
5482  # No caption, fall back to using the filename for the
5483  # alt text
5484  $params['frame']['alt'] = $title->getText();
5485  }
5486  }
5487  # Use the "caption" for the tooltip text
5488  $params['frame']['title'] = $this->stripAltText( $caption, $holders );
5489  }
5490 
5491  wfRunHooks( 'ParserMakeImageParams', array( $title, $file, &$params, $this ) );
5492 
5493  # Linker does the rest
5494  $time = isset( $options['time'] ) ? $options['time'] : false;
5495  $ret = Linker::makeImageLink( $this, $title, $file, $params['frame'], $params['handler'],
5496  $time, $descQuery, $this->mOptions->getThumbSize() );
5497 
5498  # Give the handler a chance to modify the parser object
5499  if ( $handler ) {
5500  $handler->parserTransformHook( $this, $file );
5501  }
5502 
5503  return $ret;
5504  }
5505 
5511  protected function stripAltText( $caption, $holders ) {
5512  # Strip bad stuff out of the title (tooltip). We can't just use
5513  # replaceLinkHoldersText() here, because if this function is called
5514  # from replaceInternalLinks2(), mLinkHolders won't be up-to-date.
5515  if ( $holders ) {
5516  $tooltip = $holders->replaceText( $caption );
5517  } else {
5518  $tooltip = $this->replaceLinkHoldersText( $caption );
5519  }
5520 
5521  # make sure there are no placeholders in thumbnail attributes
5522  # that are later expanded to html- so expand them now and
5523  # remove the tags
5524  $tooltip = $this->mStripState->unstripBoth( $tooltip );
5525  $tooltip = Sanitizer::stripAllTags( $tooltip );
5526 
5527  return $tooltip;
5528  }
5529 
5534  function disableCache() {
5535  wfDebug( "Parser output marked as uncacheable.\n" );
5536  if ( !$this->mOutput ) {
5537  throw new MWException( __METHOD__ .
5538  " can only be called when actually parsing something" );
5539  }
5540  $this->mOutput->setCacheTime( -1 ); // old style, for compatibility
5541  $this->mOutput->updateCacheExpiry( 0 ); // new style, for consistency
5542  }
5543 
5552  function attributeStripCallback( &$text, $frame = false ) {
5553  $text = $this->replaceVariables( $text, $frame );
5554  $text = $this->mStripState->unstripBoth( $text );
5555  return $text;
5556  }
5557 
5563  function getTags() {
5564  return array_merge( array_keys( $this->mTransparentTagHooks ), array_keys( $this->mTagHooks ), array_keys( $this->mFunctionTagHooks ) );
5565  }
5566 
5577  function replaceTransparentTags( $text ) {
5578  $matches = array();
5579  $elements = array_keys( $this->mTransparentTagHooks );
5580  $text = self::extractTagsAndParams( $elements, $text, $matches, $this->mUniqPrefix );
5581  $replacements = array();
5582 
5583  foreach ( $matches as $marker => $data ) {
5584  list( $element, $content, $params, $tag ) = $data;
5585  $tagName = strtolower( $element );
5586  if ( isset( $this->mTransparentTagHooks[$tagName] ) ) {
5587  $output = call_user_func_array( $this->mTransparentTagHooks[$tagName], array( $content, $params, $this ) );
5588  } else {
5589  $output = $tag;
5590  }
5591  $replacements[$marker] = $output;
5592  }
5593  return strtr( $text, $replacements );
5594  }
5595 
5625  private function extractSections( $text, $section, $mode, $newText = '' ) {
5626  global $wgTitle; # not generally used but removes an ugly failure mode
5627  $this->startParse( $wgTitle, new ParserOptions, self::OT_PLAIN, true );
5628  $outText = '';
5629  $frame = $this->getPreprocessor()->newFrame();
5630 
5631  # Process section extraction flags
5632  $flags = 0;
5633  $sectionParts = explode( '-', $section );
5634  $sectionIndex = array_pop( $sectionParts );
5635  foreach ( $sectionParts as $part ) {
5636  if ( $part === 'T' ) {
5637  $flags |= self::PTD_FOR_INCLUSION;
5638  }
5639  }
5640 
5641  # Check for empty input
5642  if ( strval( $text ) === '' ) {
5643  # Only sections 0 and T-0 exist in an empty document
5644  if ( $sectionIndex == 0 ) {
5645  if ( $mode === 'get' ) {
5646  return '';
5647  } else {
5648  return $newText;
5649  }
5650  } else {
5651  if ( $mode === 'get' ) {
5652  return $newText;
5653  } else {
5654  return $text;
5655  }
5656  }
5657  }
5658 
5659  # Preprocess the text
5660  $root = $this->preprocessToDom( $text, $flags );
5661 
5662  # <h> nodes indicate section breaks
5663  # They can only occur at the top level, so we can find them by iterating the root's children
5664  $node = $root->getFirstChild();
5665 
5666  # Find the target section
5667  if ( $sectionIndex == 0 ) {
5668  # Section zero doesn't nest, level=big
5669  $targetLevel = 1000;
5670  } else {
5671  while ( $node ) {
5672  if ( $node->getName() === 'h' ) {
5673  $bits = $node->splitHeading();
5674  if ( $bits['i'] == $sectionIndex ) {
5675  $targetLevel = $bits['level'];
5676  break;
5677  }
5678  }
5679  if ( $mode === 'replace' ) {
5680  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5681  }
5682  $node = $node->getNextSibling();
5683  }
5684  }
5685 
5686  if ( !$node ) {
5687  # Not found
5688  if ( $mode === 'get' ) {
5689  return $newText;
5690  } else {
5691  return $text;
5692  }
5693  }
5694 
5695  # Find the end of the section, including nested sections
5696  do {
5697  if ( $node->getName() === 'h' ) {
5698  $bits = $node->splitHeading();
5699  $curLevel = $bits['level'];
5700  if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5701  break;
5702  }
5703  }
5704  if ( $mode === 'get' ) {
5705  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5706  }
5707  $node = $node->getNextSibling();
5708  } while ( $node );
5709 
5710  # Write out the remainder (in replace mode only)
5711  if ( $mode === 'replace' ) {
5712  # Output the replacement text
5713  # Add two newlines on -- trailing whitespace in $newText is conventionally
5714  # stripped by the editor, so we need both newlines to restore the paragraph gap
5715  # Only add trailing whitespace if there is newText
5716  if ( $newText != "" ) {
5717  $outText .= $newText . "\n\n";
5718  }
5719 
5720  while ( $node ) {
5721  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5722  $node = $node->getNextSibling();
5723  }
5724  }
5725 
5726  if ( is_string( $outText ) ) {
5727  # Re-insert stripped tags
5728  $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5729  }
5730 
5731  return $outText;
5732  }
5733 
5746  public function getSection( $text, $section, $deftext = '' ) {
5747  return $this->extractSections( $text, $section, "get", $deftext );
5748  }
5749 
5760  public function replaceSection( $oldtext, $section, $text ) {
5761  return $this->extractSections( $oldtext, $section, "replace", $text );
5762  }
5763 
5769  function getRevisionId() {
5770  return $this->mRevisionId;
5771  }
5772 
5779  public function getRevisionObject() {
5780  if ( !is_null( $this->mRevisionObject ) ) {
5781  return $this->mRevisionObject;
5782  }
5783  if ( is_null( $this->mRevisionId ) ) {
5784  return null;
5785  }
5786 
5787  $this->mRevisionObject = Revision::newFromId( $this->mRevisionId );
5788  return $this->mRevisionObject;
5789  }
5790 
5795  function getRevisionTimestamp() {
5796  if ( is_null( $this->mRevisionTimestamp ) ) {
5797  wfProfileIn( __METHOD__ );
5798 
5800 
5801  $revObject = $this->getRevisionObject();
5802  $timestamp = $revObject ? $revObject->getTimestamp() : wfTimestampNow();
5803 
5804  # The cryptic '' timezone parameter tells to use the site-default
5805  # timezone offset instead of the user settings.
5806  #
5807  # Since this value will be saved into the parser cache, served
5808  # to other users, and potentially even used inside links and such,
5809  # it needs to be consistent for all visitors.
5810  $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
5811 
5812  wfProfileOut( __METHOD__ );
5813  }
5814  return $this->mRevisionTimestamp;
5815  }
5816 
5822  function getRevisionUser() {
5823  if ( is_null( $this->mRevisionUser ) ) {
5824  $revObject = $this->getRevisionObject();
5825 
5826  # if this template is subst: the revision id will be blank,
5827  # so just use the current user's name
5828  if ( $revObject ) {
5829  $this->mRevisionUser = $revObject->getUserText();
5830  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
5831  $this->mRevisionUser = $this->getUser()->getName();
5832  }
5833  }
5834  return $this->mRevisionUser;
5835  }
5836 
5842  function getRevisionSize() {
5843  if ( is_null( $this->mRevisionSize ) ) {
5844  $revObject = $this->getRevisionObject();
5845 
5846  # if this variable is subst: the revision id will be blank,
5847  # so just use the parser input size, because the own substituation
5848  # will change the size.
5849  if ( $revObject ) {
5850  $this->mRevisionSize = $revObject->getSize();
5851  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
5852  $this->mRevisionSize = $this->mInputSize;
5853  }
5854  }
5855  return $this->mRevisionSize;
5856  }
5857 
5863  public function setDefaultSort( $sort ) {
5864  $this->mDefaultSort = $sort;
5865  $this->mOutput->setProperty( 'defaultsort', $sort );
5866  }
5867 
5878  public function getDefaultSort() {
5879  if ( $this->mDefaultSort !== false ) {
5880  return $this->mDefaultSort;
5881  } else {
5882  return '';
5883  }
5884  }
5885 
5892  public function getCustomDefaultSort() {
5893  return $this->mDefaultSort;
5894  }
5895 
5905  public function guessSectionNameFromWikiText( $text ) {
5906  # Strip out wikitext links(they break the anchor)
5907  $text = $this->stripSectionName( $text );
5909  return '#' . Sanitizer::escapeId( $text, 'noninitial' );
5910  }
5911 
5920  public function guessLegacySectionNameFromWikiText( $text ) {
5921  # Strip out wikitext links(they break the anchor)
5922  $text = $this->stripSectionName( $text );
5924  return '#' . Sanitizer::escapeId( $text, array( 'noninitial', 'legacy' ) );
5925  }
5926 
5941  public function stripSectionName( $text ) {
5942  # Strip internal link markup
5943  $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
5944  $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
5945 
5946  # Strip external link markup
5947  # @todo FIXME: Not tolerant to blank link text
5948  # I.E. [https://www.mediawiki.org] will render as [1] or something depending
5949  # on how many empty links there are on the page - need to figure that out.
5950  $text = preg_replace( '/\[(?i:' . $this->mUrlProtocols . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
5951 
5952  # Parse wikitext quotes (italics & bold)
5953  $text = $this->doQuotes( $text );
5954 
5955  # Strip HTML tags
5956  $text = StringUtils::delimiterReplace( '<', '>', '', $text );
5957  return $text;
5958  }
5959 
5970  function testSrvus( $text, Title $title, ParserOptions $options, $outputType = self::OT_HTML ) {
5971  $this->startParse( $title, $options, $outputType, true );
5972 
5973  $text = $this->replaceVariables( $text );
5974  $text = $this->mStripState->unstripBoth( $text );
5975  $text = Sanitizer::removeHTMLtags( $text );
5976  return $text;
5977  }
5978 
5985  function testPst( $text, Title $title, ParserOptions $options ) {
5986  return $this->preSaveTransform( $text, $title, $options->getUser(), $options );
5987  }
5988 
5995  function testPreprocess( $text, Title $title, ParserOptions $options ) {
5996  return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
5997  }
5998 
6015  function markerSkipCallback( $s, $callback ) {
6016  $i = 0;
6017  $out = '';
6018  while ( $i < strlen( $s ) ) {
6019  $markerStart = strpos( $s, $this->mUniqPrefix, $i );
6020  if ( $markerStart === false ) {
6021  $out .= call_user_func( $callback, substr( $s, $i ) );
6022  break;
6023  } else {
6024  $out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) );
6025  $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
6026  if ( $markerEnd === false ) {
6027  $out .= substr( $s, $markerStart );
6028  break;
6029  } else {
6030  $markerEnd += strlen( self::MARKER_SUFFIX );
6031  $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
6032  $i = $markerEnd;
6033  }
6034  }
6035  }
6036  return $out;
6037  }
6038 
6045  function killMarkers( $text ) {
6046  return $this->mStripState->killMarkers( $text );
6047  }
6048 
6065  function serializeHalfParsedText( $text ) {
6066  wfProfileIn( __METHOD__ );
6067  $data = array(
6068  'text' => $text,
6069  'version' => self::HALF_PARSED_VERSION,
6070  'stripState' => $this->mStripState->getSubState( $text ),
6071  'linkHolders' => $this->mLinkHolders->getSubArray( $text )
6072  );
6073  wfProfileOut( __METHOD__ );
6074  return $data;
6075  }
6076 
6092  function unserializeHalfParsedText( $data ) {
6093  if ( !isset( $data['version'] ) || $data['version'] != self::HALF_PARSED_VERSION ) {
6094  throw new MWException( __METHOD__ . ': invalid version' );
6095  }
6096 
6097  # First, extract the strip state.
6098  $texts = array( $data['text'] );
6099  $texts = $this->mStripState->merge( $data['stripState'], $texts );
6100 
6101  # Now renumber links
6102  $texts = $this->mLinkHolders->mergeForeign( $data['linkHolders'], $texts );
6103 
6104  # Should be good to go.
6105  return $texts[0];
6106  }
6107 
6117  function isValidHalfParsedText( $data ) {
6118  return isset( $data['version'] ) && $data['version'] == self::HALF_PARSED_VERSION;
6119  }
6120 
6129  public function parseWidthParam( $value ) {
6130  $parsedWidthParam = array();
6131  if ( $value === '' ) {
6132  return $parsedWidthParam;
6133  }
6134  $m = array();
6135  # (bug 13500) In both cases (width/height and width only),
6136  # permit trailing "px" for backward compatibility.
6137  if ( preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
6138  $width = intval( $m[1] );
6139  $height = intval( $m[2] );
6140  $parsedWidthParam['width'] = $width;
6141  $parsedWidthParam['height'] = $height;
6142  } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) {
6143  $width = intval( $value );
6144  $parsedWidthParam['width'] = $width;
6145  }
6146  return $parsedWidthParam;
6147  }
6148 }
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:1297
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:1735
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:1099
$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:524
Sanitizer\cleanUrl
static cleanUrl( $url)
Definition: Sanitizer.php:1768
$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:1004
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:1316
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:1183
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:1413
$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