MediaWiki  master
Parser.php
Go to the documentation of this file.
1 <?php
25 
69 class Parser {
75  const VERSION = '1.6.4';
76 
82 
83  # Flags for Parser::setFunctionHook
84  const SFH_NO_HASH = 1;
85  const SFH_OBJECT_ARGS = 2;
86 
87  # Constants needed for external link processing
88  # Everything except bracket, space, or control characters
89  # \p{Zs} is unicode 'separator, space' category. It covers the space 0x20
90  # as well as U+3000 is IDEOGRAPHIC SPACE for bug 19052
91  const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F\p{Zs}]';
92  # Simplified expression to match an IPv4 or IPv6 address, or
93  # at least one character of a host name (embeds EXT_LINK_URL_CLASS)
94  const EXT_LINK_ADDR = '(?:[0-9.]+|\\[(?i:[0-9a-f:.]+)\\]|[^][<>"\\x00-\\x20\\x7F\p{Zs}])';
95  # RegExp to make image URLs (embeds IPv6 part of EXT_LINK_ADDR)
96  // @codingStandardsIgnoreStart Generic.Files.LineLength
97  const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)((?:\\[(?i:[0-9a-f:.]+)\\])?[^][<>"\\x00-\\x20\\x7F\p{Zs}]+)
98  \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sxu';
99  // @codingStandardsIgnoreEnd
100 
101  # Regular expression for a non-newline space
102  const SPACE_NOT_NL = '(?:\t|&nbsp;|&\#0*160;|&\#[Xx]0*[Aa]0;|\p{Zs})';
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()
112  const OT_MSG = 3;
113  const OT_PLAIN = 4; # like extractSections() - portions of the original are returned unchanged.
114 
132  const MARKER_SUFFIX = "-QINU`\"'\x7f";
133  const MARKER_PREFIX = "\x7f'\"`UNIQ-";
134 
135  # Markers used for wrapping the table of contents
136  const TOC_START = '<mw:toc>';
137  const TOC_END = '</mw:toc>';
138 
139  # Persistent:
140  public $mTagHooks = [];
142  public $mFunctionHooks = [];
143  public $mFunctionSynonyms = [ 0 => [], 1 => [] ];
144  public $mFunctionTagHooks = [];
145  public $mStripList = [];
146  public $mDefaultStripList = [];
147  public $mVarCache = [];
148  public $mImageParams = [];
150  public $mMarkerIndex = 0;
151  public $mFirstCall = true;
152 
153  # Initialised by initialiseVariables()
154 
158  public $mVariables;
159 
163  public $mSubstWords;
164  # Initialised in constructor
166 
167  # Initialized in getPreprocessor()
168 
170 
171  # Cleared with clearState():
172 
175  public $mOutput;
176  public $mAutonumber;
177 
181  public $mStripState;
182 
188 
189  public $mLinkID;
193  public $mExpensiveFunctionCount; # number of expensive parser function calls
195 
199  public $mUser; # User object; only used when doing pre-save transform
200 
201  # Temporary
202  # These are variables reset at least once per parse regardless of $clearState
203 
207  public $mOptions;
208 
212  public $mTitle; # Title context, used for self-link rendering and similar things
213  public $mOutputType; # Output type, one of the OT_xxx constants
214  public $ot; # Shortcut alias, see setOutputType()
215  public $mRevisionObject; # The revision object of the specified revision ID
216  public $mRevisionId; # ID to display in {{REVISIONID}} tags
217  public $mRevisionTimestamp; # The timestamp of the specified revision ID
218  public $mRevisionUser; # User to display in {{REVISIONUSER}} tag
219  public $mRevisionSize; # Size to display in {{REVISIONSIZE}} variable
220  public $mRevIdForTs; # The revision ID which was used to fetch the timestamp
221  public $mInputSize = false; # For {{PAGESIZE}} on current page.
222 
228 
235 
243 
248  public $mInParse = false;
249 
251  protected $mProfiler;
252 
256  protected $mLinkRenderer;
257 
261  public function __construct( $conf = [] ) {
262  $this->mConf = $conf;
263  $this->mUrlProtocols = wfUrlProtocols();
264  $this->mExtLinkBracketedRegex = '/\[(((?i)' . $this->mUrlProtocols . ')' .
265  self::EXT_LINK_ADDR .
266  self::EXT_LINK_URL_CLASS . '*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F]*?)\]/Su';
267  if ( isset( $conf['preprocessorClass'] ) ) {
268  $this->mPreprocessorClass = $conf['preprocessorClass'];
269  } elseif ( defined( 'HPHP_VERSION' ) ) {
270  # Preprocessor_Hash is much faster than Preprocessor_DOM under HipHop
271  $this->mPreprocessorClass = 'Preprocessor_Hash';
272  } elseif ( extension_loaded( 'domxml' ) ) {
273  # PECL extension that conflicts with the core DOM extension (bug 13770)
274  wfDebug( "Warning: you have the obsolete domxml extension for PHP. Please remove it!\n" );
275  $this->mPreprocessorClass = 'Preprocessor_Hash';
276  } elseif ( extension_loaded( 'dom' ) ) {
277  $this->mPreprocessorClass = 'Preprocessor_DOM';
278  } else {
279  $this->mPreprocessorClass = 'Preprocessor_Hash';
280  }
281  wfDebug( __CLASS__ . ": using preprocessor: {$this->mPreprocessorClass}\n" );
282  }
283 
287  public function __destruct() {
288  if ( isset( $this->mLinkHolders ) ) {
289  unset( $this->mLinkHolders );
290  }
291  foreach ( $this as $name => $value ) {
292  unset( $this->$name );
293  }
294  }
295 
299  public function __clone() {
300  $this->mInParse = false;
301 
302  // Bug 56226: When you create a reference "to" an object field, that
303  // makes the object field itself be a reference too (until the other
304  // reference goes out of scope). When cloning, any field that's a
305  // reference is copied as a reference in the new object. Both of these
306  // are defined PHP5 behaviors, as inconvenient as it is for us when old
307  // hooks from PHP4 days are passing fields by reference.
308  foreach ( [ 'mStripState', 'mVarCache' ] as $k ) {
309  // Make a non-reference copy of the field, then rebind the field to
310  // reference the new copy.
311  $tmp = $this->$k;
312  $this->$k =& $tmp;
313  unset( $tmp );
314  }
315 
316  Hooks::run( 'ParserCloned', [ $this ] );
317  }
318 
322  public function firstCallInit() {
323  if ( !$this->mFirstCall ) {
324  return;
325  }
326  $this->mFirstCall = false;
327 
329  CoreTagHooks::register( $this );
330  $this->initialiseVariables();
331 
332  Hooks::run( 'ParserFirstCallInit', [ &$this ] );
333  }
334 
340  public function clearState() {
341  if ( $this->mFirstCall ) {
342  $this->firstCallInit();
343  }
344  $this->mOutput = new ParserOutput;
345  $this->mOptions->registerWatcher( [ $this->mOutput, 'recordOption' ] );
346  $this->mAutonumber = 0;
347  $this->mIncludeCount = [];
348  $this->mLinkHolders = new LinkHolderArray( $this );
349  $this->mLinkID = 0;
350  $this->mRevisionObject = $this->mRevisionTimestamp =
351  $this->mRevisionId = $this->mRevisionUser = $this->mRevisionSize = null;
352  $this->mVarCache = [];
353  $this->mUser = null;
354  $this->mLangLinkLanguages = [];
355  $this->currentRevisionCache = null;
356 
357  $this->mStripState = new StripState;
358 
359  # Clear these on every parse, bug 4549
360  $this->mTplRedirCache = $this->mTplDomCache = [];
361 
362  $this->mShowToc = true;
363  $this->mForceTocPosition = false;
364  $this->mIncludeSizes = [
365  'post-expand' => 0,
366  'arg' => 0,
367  ];
368  $this->mPPNodeCount = 0;
369  $this->mGeneratedPPNodeCount = 0;
370  $this->mHighestExpansionDepth = 0;
371  $this->mDefaultSort = false;
372  $this->mHeadings = [];
373  $this->mDoubleUnderscores = [];
374  $this->mExpensiveFunctionCount = 0;
375 
376  # Fix cloning
377  if ( isset( $this->mPreprocessor ) && $this->mPreprocessor->parser !== $this ) {
378  $this->mPreprocessor = null;
379  }
380 
381  $this->mProfiler = new SectionProfiler();
382 
383  Hooks::run( 'ParserClearState', [ &$this ] );
384  }
385 
398  public function parse( $text, Title $title, ParserOptions $options,
399  $linestart = true, $clearState = true, $revid = null
400  ) {
407 
408  if ( $clearState ) {
409  // We use U+007F DELETE to construct strip markers, so we have to make
410  // sure that this character does not occur in the input text.
411  $text = strtr( $text, "\x7f", "?" );
412  $magicScopeVariable = $this->lock();
413  }
414 
415  $this->startParse( $title, $options, self::OT_HTML, $clearState );
416 
417  $this->currentRevisionCache = null;
418  $this->mInputSize = strlen( $text );
419  if ( $this->mOptions->getEnableLimitReport() ) {
420  $this->mOutput->resetParseStartTime();
421  }
422 
423  $oldRevisionId = $this->mRevisionId;
424  $oldRevisionObject = $this->mRevisionObject;
425  $oldRevisionTimestamp = $this->mRevisionTimestamp;
426  $oldRevisionUser = $this->mRevisionUser;
427  $oldRevisionSize = $this->mRevisionSize;
428  if ( $revid !== null ) {
429  $this->mRevisionId = $revid;
430  $this->mRevisionObject = null;
431  $this->mRevisionTimestamp = null;
432  $this->mRevisionUser = null;
433  $this->mRevisionSize = null;
434  }
435 
436  Hooks::run( 'ParserBeforeStrip', [ &$this, &$text, &$this->mStripState ] );
437  # No more strip!
438  Hooks::run( 'ParserAfterStrip', [ &$this, &$text, &$this->mStripState ] );
439  $text = $this->internalParse( $text );
440  Hooks::run( 'ParserAfterParse', [ &$this, &$text, &$this->mStripState ] );
441 
442  $text = $this->internalParseHalfParsed( $text, true, $linestart );
443 
451  if ( !( $options->getDisableTitleConversion()
452  || isset( $this->mDoubleUnderscores['nocontentconvert'] )
453  || isset( $this->mDoubleUnderscores['notitleconvert'] )
454  || $this->mOutput->getDisplayTitle() !== false )
455  ) {
456  $convruletitle = $this->getConverterLanguage()->getConvRuleTitle();
457  if ( $convruletitle ) {
458  $this->mOutput->setTitleText( $convruletitle );
459  } else {
460  $titleText = $this->getConverterLanguage()->convertTitle( $title );
461  $this->mOutput->setTitleText( $titleText );
462  }
463  }
464 
465  if ( $this->mExpensiveFunctionCount > $this->mOptions->getExpensiveParserFunctionLimit() ) {
466  $this->limitationWarn( 'expensive-parserfunction',
467  $this->mExpensiveFunctionCount,
468  $this->mOptions->getExpensiveParserFunctionLimit()
469  );
470  }
471 
472  # Information on include size limits, for the benefit of users who try to skirt them
473  if ( $this->mOptions->getEnableLimitReport() ) {
474  $max = $this->mOptions->getMaxIncludeSize();
475 
476  $cpuTime = $this->mOutput->getTimeSinceStart( 'cpu' );
477  if ( $cpuTime !== null ) {
478  $this->mOutput->setLimitReportData( 'limitreport-cputime',
479  sprintf( "%.3f", $cpuTime )
480  );
481  }
482 
483  $wallTime = $this->mOutput->getTimeSinceStart( 'wall' );
484  $this->mOutput->setLimitReportData( 'limitreport-walltime',
485  sprintf( "%.3f", $wallTime )
486  );
487 
488  $this->mOutput->setLimitReportData( 'limitreport-ppvisitednodes',
489  [ $this->mPPNodeCount, $this->mOptions->getMaxPPNodeCount() ]
490  );
491  $this->mOutput->setLimitReportData( 'limitreport-ppgeneratednodes',
492  [ $this->mGeneratedPPNodeCount, $this->mOptions->getMaxGeneratedPPNodeCount() ]
493  );
494  $this->mOutput->setLimitReportData( 'limitreport-postexpandincludesize',
495  [ $this->mIncludeSizes['post-expand'], $max ]
496  );
497  $this->mOutput->setLimitReportData( 'limitreport-templateargumentsize',
498  [ $this->mIncludeSizes['arg'], $max ]
499  );
500  $this->mOutput->setLimitReportData( 'limitreport-expansiondepth',
501  [ $this->mHighestExpansionDepth, $this->mOptions->getMaxPPExpandDepth() ]
502  );
503  $this->mOutput->setLimitReportData( 'limitreport-expensivefunctioncount',
504  [ $this->mExpensiveFunctionCount,
505  $this->mOptions->getExpensiveParserFunctionLimit() ]
506  );
507  Hooks::run( 'ParserLimitReportPrepare', [ $this, $this->mOutput ] );
508 
509  $limitReport = '';
510  Hooks::run( 'ParserLimitReport', [ $this, &$limitReport ] );
511  if ( $limitReport != '' ) {
512  // Sanitize for comment. Note '‐' in the replacement is U+2010,
513  // which looks much like the problematic '-'.
514  $limitReport = str_replace( [ '-', '&' ], [ '‐', '&amp;' ], $limitReport );
515  $text .= "\n<!-- \nNewPP limit report\n$limitReport-->\n";
516  }
517 
518  // Add on template profiling data in human/machine readable way
519  $dataByFunc = $this->mProfiler->getFunctionStats();
520  uasort( $dataByFunc, function ( $a, $b ) {
521  return $a['real'] < $b['real']; // descending order
522  } );
523  $profileReport = [];
524  foreach ( array_slice( $dataByFunc, 0, 10 ) as $item ) {
525  $profileReport[] = sprintf( "%6.2f%% %8.3f %6d %s",
526  $item['%real'], $item['real'], $item['calls'], $item['name'] );
527  }
528  $this->mOutput->setLimitReportData( 'limitreport-timingprofile', $profileReport );
529 
530  // Add other cache related metadata
531  if ( $wgShowHostnames ) {
532  $this->mOutput->setLimitReportData( 'cachereport-origin', wfHostname() );
533  }
534  $this->mOutput->setLimitReportData( 'cachereport-timestamp',
535  $this->mOutput->getCacheTime() );
536  $this->mOutput->setLimitReportData( 'cachereport-ttl',
537  $this->mOutput->getCacheExpiry() );
538  $this->mOutput->setLimitReportData( 'cachereport-transientcontent',
539  $this->mOutput->hasDynamicContent() );
540 
541  if ( $this->mGeneratedPPNodeCount
542  > $this->mOptions->getMaxGeneratedPPNodeCount() / 10
543  ) {
544  wfDebugLog( 'generated-pp-node-count', $this->mGeneratedPPNodeCount . ' ' .
545  $this->mTitle->getPrefixedDBkey() );
546  }
547  }
548  $this->mOutput->setText( $text );
549 
550  $this->mRevisionId = $oldRevisionId;
551  $this->mRevisionObject = $oldRevisionObject;
552  $this->mRevisionTimestamp = $oldRevisionTimestamp;
553  $this->mRevisionUser = $oldRevisionUser;
554  $this->mRevisionSize = $oldRevisionSize;
555  $this->mInputSize = false;
556  $this->currentRevisionCache = null;
557 
558  return $this->mOutput;
559  }
560 
583  public function recursiveTagParse( $text, $frame = false ) {
584  Hooks::run( 'ParserBeforeStrip', [ &$this, &$text, &$this->mStripState ] );
585  Hooks::run( 'ParserAfterStrip', [ &$this, &$text, &$this->mStripState ] );
586  $text = $this->internalParse( $text, false, $frame );
587  return $text;
588  }
589 
607  public function recursiveTagParseFully( $text, $frame = false ) {
608  $text = $this->recursiveTagParse( $text, $frame );
609  $text = $this->internalParseHalfParsed( $text, false );
610  return $text;
611  }
612 
624  public function preprocess( $text, Title $title = null,
625  ParserOptions $options, $revid = null, $frame = false
626  ) {
627  $magicScopeVariable = $this->lock();
628  $this->startParse( $title, $options, self::OT_PREPROCESS, true );
629  if ( $revid !== null ) {
630  $this->mRevisionId = $revid;
631  }
632  Hooks::run( 'ParserBeforeStrip', [ &$this, &$text, &$this->mStripState ] );
633  Hooks::run( 'ParserAfterStrip', [ &$this, &$text, &$this->mStripState ] );
634  $text = $this->replaceVariables( $text, $frame );
635  $text = $this->mStripState->unstripBoth( $text );
636  return $text;
637  }
638 
648  public function recursivePreprocess( $text, $frame = false ) {
649  $text = $this->replaceVariables( $text, $frame );
650  $text = $this->mStripState->unstripBoth( $text );
651  return $text;
652  }
653 
667  public function getPreloadText( $text, Title $title, ParserOptions $options, $params = [] ) {
668  $msg = new RawMessage( $text );
669  $text = $msg->params( $params )->plain();
670 
671  # Parser (re)initialisation
672  $magicScopeVariable = $this->lock();
673  $this->startParse( $title, $options, self::OT_PLAIN, true );
674 
676  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
677  $text = $this->getPreprocessor()->newFrame()->expand( $dom, $flags );
678  $text = $this->mStripState->unstripBoth( $text );
679  return $text;
680  }
681 
688  public static function getRandomString() {
689  wfDeprecated( __METHOD__, '1.26' );
690  return wfRandomString( 16 );
691  }
692 
699  public function setUser( $user ) {
700  $this->mUser = $user;
701  }
702 
709  public function uniqPrefix() {
710  wfDeprecated( __METHOD__, '1.26' );
711  return self::MARKER_PREFIX;
712  }
713 
719  public function setTitle( $t ) {
720  if ( !$t ) {
721  $t = Title::newFromText( 'NO TITLE' );
722  }
723 
724  if ( $t->hasFragment() ) {
725  # Strip the fragment to avoid various odd effects
726  $this->mTitle = $t->createFragmentTarget( '' );
727  } else {
728  $this->mTitle = $t;
729  }
730  }
731 
737  public function getTitle() {
738  return $this->mTitle;
739  }
740 
747  public function Title( $x = null ) {
748  return wfSetVar( $this->mTitle, $x );
749  }
750 
756  public function setOutputType( $ot ) {
757  $this->mOutputType = $ot;
758  # Shortcut alias
759  $this->ot = [
760  'html' => $ot == self::OT_HTML,
761  'wiki' => $ot == self::OT_WIKI,
762  'pre' => $ot == self::OT_PREPROCESS,
763  'plain' => $ot == self::OT_PLAIN,
764  ];
765  }
766 
773  public function OutputType( $x = null ) {
774  return wfSetVar( $this->mOutputType, $x );
775  }
776 
782  public function getOutput() {
783  return $this->mOutput;
784  }
785 
791  public function getOptions() {
792  return $this->mOptions;
793  }
794 
801  public function Options( $x = null ) {
802  return wfSetVar( $this->mOptions, $x );
803  }
804 
808  public function nextLinkID() {
809  return $this->mLinkID++;
810  }
811 
815  public function setLinkID( $id ) {
816  $this->mLinkID = $id;
817  }
818 
823  public function getFunctionLang() {
824  return $this->getTargetLanguage();
825  }
826 
836  public function getTargetLanguage() {
837  $target = $this->mOptions->getTargetLanguage();
838 
839  if ( $target !== null ) {
840  return $target;
841  } elseif ( $this->mOptions->getInterfaceMessage() ) {
842  return $this->mOptions->getUserLangObj();
843  } elseif ( is_null( $this->mTitle ) ) {
844  throw new MWException( __METHOD__ . ': $this->mTitle is null' );
845  }
846 
847  return $this->mTitle->getPageLanguage();
848  }
849 
854  public function getConverterLanguage() {
855  return $this->getTargetLanguage();
856  }
857 
864  public function getUser() {
865  if ( !is_null( $this->mUser ) ) {
866  return $this->mUser;
867  }
868  return $this->mOptions->getUser();
869  }
870 
876  public function getPreprocessor() {
877  if ( !isset( $this->mPreprocessor ) ) {
878  $class = $this->mPreprocessorClass;
879  $this->mPreprocessor = new $class( $this );
880  }
881  return $this->mPreprocessor;
882  }
883 
890  public function getLinkRenderer() {
891  if ( !$this->mLinkRenderer ) {
892  $this->mLinkRenderer = MediaWikiServices::getInstance()
893  ->getLinkRendererFactory()->create();
894  $this->mLinkRenderer->setStubThreshold(
895  $this->getOptions()->getStubThreshold()
896  );
897  }
898 
899  return $this->mLinkRenderer;
900  }
901 
923  public static function extractTagsAndParams( $elements, $text, &$matches, $uniq_prefix = null ) {
924  if ( $uniq_prefix !== null ) {
925  wfDeprecated( __METHOD__ . ' called with $prefix argument', '1.26' );
926  }
927  static $n = 1;
928  $stripped = '';
929  $matches = [];
930 
931  $taglist = implode( '|', $elements );
932  $start = "/<($taglist)(\\s+[^>]*?|\\s*?)(\/?" . ">)|<(!--)/i";
933 
934  while ( $text != '' ) {
935  $p = preg_split( $start, $text, 2, PREG_SPLIT_DELIM_CAPTURE );
936  $stripped .= $p[0];
937  if ( count( $p ) < 5 ) {
938  break;
939  }
940  if ( count( $p ) > 5 ) {
941  # comment
942  $element = $p[4];
943  $attributes = '';
944  $close = '';
945  $inside = $p[5];
946  } else {
947  # tag
948  $element = $p[1];
949  $attributes = $p[2];
950  $close = $p[3];
951  $inside = $p[4];
952  }
953 
954  $marker = self::MARKER_PREFIX . "-$element-" . sprintf( '%08X', $n++ ) . self::MARKER_SUFFIX;
955  $stripped .= $marker;
956 
957  if ( $close === '/>' ) {
958  # Empty element tag, <tag />
959  $content = null;
960  $text = $inside;
961  $tail = null;
962  } else {
963  if ( $element === '!--' ) {
964  $end = '/(-->)/';
965  } else {
966  $end = "/(<\\/$element\\s*>)/i";
967  }
968  $q = preg_split( $end, $inside, 2, PREG_SPLIT_DELIM_CAPTURE );
969  $content = $q[0];
970  if ( count( $q ) < 3 ) {
971  # No end tag -- let it run out to the end of the text.
972  $tail = '';
973  $text = '';
974  } else {
975  $tail = $q[1];
976  $text = $q[2];
977  }
978  }
979 
980  $matches[$marker] = [ $element,
981  $content,
982  Sanitizer::decodeTagAttributes( $attributes ),
983  "<$element$attributes$close$content$tail" ];
984  }
985  return $stripped;
986  }
987 
993  public function getStripList() {
994  return $this->mStripList;
995  }
996 
1006  public function insertStripItem( $text ) {
1007  $marker = self::MARKER_PREFIX . "-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
1008  $this->mMarkerIndex++;
1009  $this->mStripState->addGeneral( $marker, $text );
1010  return $marker;
1011  }
1012 
1020  public function doTableStuff( $text ) {
1021 
1022  $lines = StringUtils::explode( "\n", $text );
1023  $out = '';
1024  $td_history = []; # Is currently a td tag open?
1025  $last_tag_history = []; # Save history of last lag activated (td, th or caption)
1026  $tr_history = []; # Is currently a tr tag open?
1027  $tr_attributes = []; # history of tr attributes
1028  $has_opened_tr = []; # Did this table open a <tr> element?
1029  $indent_level = 0; # indent level of the table
1030 
1031  foreach ( $lines as $outLine ) {
1032  $line = trim( $outLine );
1033 
1034  if ( $line === '' ) { # empty line, go to next line
1035  $out .= $outLine . "\n";
1036  continue;
1037  }
1038 
1039  $first_character = $line[0];
1040  $first_two = substr( $line, 0, 2 );
1041  $matches = [];
1042 
1043  if ( preg_match( '/^(:*)\s*\{\|(.*)$/', $line, $matches ) ) {
1044  # First check if we are starting a new table
1045  $indent_level = strlen( $matches[1] );
1046 
1047  $attributes = $this->mStripState->unstripBoth( $matches[2] );
1048  $attributes = Sanitizer::fixTagAttributes( $attributes, 'table' );
1049 
1050  $outLine = str_repeat( '<dl><dd>', $indent_level ) . "<table{$attributes}>";
1051  array_push( $td_history, false );
1052  array_push( $last_tag_history, '' );
1053  array_push( $tr_history, false );
1054  array_push( $tr_attributes, '' );
1055  array_push( $has_opened_tr, false );
1056  } elseif ( count( $td_history ) == 0 ) {
1057  # Don't do any of the following
1058  $out .= $outLine . "\n";
1059  continue;
1060  } elseif ( $first_two === '|}' ) {
1061  # We are ending a table
1062  $line = '</table>' . substr( $line, 2 );
1063  $last_tag = array_pop( $last_tag_history );
1064 
1065  if ( !array_pop( $has_opened_tr ) ) {
1066  $line = "<tr><td></td></tr>{$line}";
1067  }
1068 
1069  if ( array_pop( $tr_history ) ) {
1070  $line = "</tr>{$line}";
1071  }
1072 
1073  if ( array_pop( $td_history ) ) {
1074  $line = "</{$last_tag}>{$line}";
1075  }
1076  array_pop( $tr_attributes );
1077  $outLine = $line . str_repeat( '</dd></dl>', $indent_level );
1078  } elseif ( $first_two === '|-' ) {
1079  # Now we have a table row
1080  $line = preg_replace( '#^\|-+#', '', $line );
1081 
1082  # Whats after the tag is now only attributes
1083  $attributes = $this->mStripState->unstripBoth( $line );
1084  $attributes = Sanitizer::fixTagAttributes( $attributes, 'tr' );
1085  array_pop( $tr_attributes );
1086  array_push( $tr_attributes, $attributes );
1087 
1088  $line = '';
1089  $last_tag = array_pop( $last_tag_history );
1090  array_pop( $has_opened_tr );
1091  array_push( $has_opened_tr, true );
1092 
1093  if ( array_pop( $tr_history ) ) {
1094  $line = '</tr>';
1095  }
1096 
1097  if ( array_pop( $td_history ) ) {
1098  $line = "</{$last_tag}>{$line}";
1099  }
1100 
1101  $outLine = $line;
1102  array_push( $tr_history, false );
1103  array_push( $td_history, false );
1104  array_push( $last_tag_history, '' );
1105  } elseif ( $first_character === '|'
1106  || $first_character === '!'
1107  || $first_two === '|+'
1108  ) {
1109  # This might be cell elements, td, th or captions
1110  if ( $first_two === '|+' ) {
1111  $first_character = '+';
1112  $line = substr( $line, 2 );
1113  } else {
1114  $line = substr( $line, 1 );
1115  }
1116 
1117  // Implies both are valid for table headings.
1118  if ( $first_character === '!' ) {
1119  $line = StringUtils::replaceMarkup( '!!', '||', $line );
1120  }
1121 
1122  # Split up multiple cells on the same line.
1123  # FIXME : This can result in improper nesting of tags processed
1124  # by earlier parser steps.
1125  $cells = explode( '||', $line );
1126 
1127  $outLine = '';
1128 
1129  # Loop through each table cell
1130  foreach ( $cells as $cell ) {
1131  $previous = '';
1132  if ( $first_character !== '+' ) {
1133  $tr_after = array_pop( $tr_attributes );
1134  if ( !array_pop( $tr_history ) ) {
1135  $previous = "<tr{$tr_after}>\n";
1136  }
1137  array_push( $tr_history, true );
1138  array_push( $tr_attributes, '' );
1139  array_pop( $has_opened_tr );
1140  array_push( $has_opened_tr, true );
1141  }
1142 
1143  $last_tag = array_pop( $last_tag_history );
1144 
1145  if ( array_pop( $td_history ) ) {
1146  $previous = "</{$last_tag}>\n{$previous}";
1147  }
1148 
1149  if ( $first_character === '|' ) {
1150  $last_tag = 'td';
1151  } elseif ( $first_character === '!' ) {
1152  $last_tag = 'th';
1153  } elseif ( $first_character === '+' ) {
1154  $last_tag = 'caption';
1155  } else {
1156  $last_tag = '';
1157  }
1158 
1159  array_push( $last_tag_history, $last_tag );
1160 
1161  # A cell could contain both parameters and data
1162  $cell_data = explode( '|', $cell, 2 );
1163 
1164  # Bug 553: Note that a '|' inside an invalid link should not
1165  # be mistaken as delimiting cell parameters
1166  if ( strpos( $cell_data[0], '[[' ) !== false ) {
1167  $cell = "{$previous}<{$last_tag}>{$cell}";
1168  } elseif ( count( $cell_data ) == 1 ) {
1169  $cell = "{$previous}<{$last_tag}>{$cell_data[0]}";
1170  } else {
1171  $attributes = $this->mStripState->unstripBoth( $cell_data[0] );
1172  $attributes = Sanitizer::fixTagAttributes( $attributes, $last_tag );
1173  $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}";
1174  }
1175 
1176  $outLine .= $cell;
1177  array_push( $td_history, true );
1178  }
1179  }
1180  $out .= $outLine . "\n";
1181  }
1182 
1183  # Closing open td, tr && table
1184  while ( count( $td_history ) > 0 ) {
1185  if ( array_pop( $td_history ) ) {
1186  $out .= "</td>\n";
1187  }
1188  if ( array_pop( $tr_history ) ) {
1189  $out .= "</tr>\n";
1190  }
1191  if ( !array_pop( $has_opened_tr ) ) {
1192  $out .= "<tr><td></td></tr>\n";
1193  }
1194 
1195  $out .= "</table>\n";
1196  }
1197 
1198  # Remove trailing line-ending (b/c)
1199  if ( substr( $out, -1 ) === "\n" ) {
1200  $out = substr( $out, 0, -1 );
1201  }
1202 
1203  # special case: don't return empty table
1204  if ( $out === "<table>\n<tr><td></td></tr>\n</table>" ) {
1205  $out = '';
1206  }
1207 
1208  return $out;
1209  }
1210 
1223  public function internalParse( $text, $isMain = true, $frame = false ) {
1224 
1225  $origText = $text;
1226 
1227  # Hook to suspend the parser in this state
1228  if ( !Hooks::run( 'ParserBeforeInternalParse', [ &$this, &$text, &$this->mStripState ] ) ) {
1229  return $text;
1230  }
1231 
1232  # if $frame is provided, then use $frame for replacing any variables
1233  if ( $frame ) {
1234  # use frame depth to infer how include/noinclude tags should be handled
1235  # depth=0 means this is the top-level document; otherwise it's an included document
1236  if ( !$frame->depth ) {
1237  $flag = 0;
1238  } else {
1239  $flag = Parser::PTD_FOR_INCLUSION;
1240  }
1241  $dom = $this->preprocessToDom( $text, $flag );
1242  $text = $frame->expand( $dom );
1243  } else {
1244  # if $frame is not provided, then use old-style replaceVariables
1245  $text = $this->replaceVariables( $text );
1246  }
1247 
1248  Hooks::run( 'InternalParseBeforeSanitize', [ &$this, &$text, &$this->mStripState ] );
1249  $text = Sanitizer::removeHTMLtags(
1250  $text,
1251  [ &$this, 'attributeStripCallback' ],
1252  false,
1253  array_keys( $this->mTransparentTagHooks ),
1254  [],
1255  [ &$this, 'addTrackingCategory' ]
1256  );
1257  Hooks::run( 'InternalParseBeforeLinks', [ &$this, &$text, &$this->mStripState ] );
1258 
1259  # Tables need to come after variable replacement for things to work
1260  # properly; putting them before other transformations should keep
1261  # exciting things like link expansions from showing up in surprising
1262  # places.
1263  $text = $this->doTableStuff( $text );
1264 
1265  $text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text );
1266 
1267  $text = $this->doDoubleUnderscore( $text );
1268 
1269  $text = $this->doHeadings( $text );
1270  $text = $this->replaceInternalLinks( $text );
1271  $text = $this->doAllQuotes( $text );
1272  $text = $this->replaceExternalLinks( $text );
1273 
1274  # replaceInternalLinks may sometimes leave behind
1275  # absolute URLs, which have to be masked to hide them from replaceExternalLinks
1276  $text = str_replace( self::MARKER_PREFIX . 'NOPARSE', '', $text );
1277 
1278  $text = $this->doMagicLinks( $text );
1279  $text = $this->formatHeadings( $text, $origText, $isMain );
1280 
1281  return $text;
1282  }
1283 
1293  private function internalParseHalfParsed( $text, $isMain = true, $linestart = true ) {
1294  $text = $this->mStripState->unstripGeneral( $text );
1295 
1296  if ( $isMain ) {
1297  Hooks::run( 'ParserAfterUnstrip', [ &$this, &$text ] );
1298  }
1299 
1300  # Clean up special characters, only run once, next-to-last before doBlockLevels
1301  $fixtags = [
1302  # french spaces, last one Guillemet-left
1303  # only if there is something before the space
1304  '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&#160;',
1305  # french spaces, Guillemet-right
1306  '/(\\302\\253) /' => '\\1&#160;',
1307  '/&#160;(!\s*important)/' => ' \\1', # Beware of CSS magic word !important, bug #11874.
1308  ];
1309  $text = preg_replace( array_keys( $fixtags ), array_values( $fixtags ), $text );
1310 
1311  $text = $this->doBlockLevels( $text, $linestart );
1312 
1313  $this->replaceLinkHolders( $text );
1314 
1322  if ( !( $this->mOptions->getDisableContentConversion()
1323  || isset( $this->mDoubleUnderscores['nocontentconvert'] ) )
1324  ) {
1325  if ( !$this->mOptions->getInterfaceMessage() ) {
1326  # The position of the convert() call should not be changed. it
1327  # assumes that the links are all replaced and the only thing left
1328  # is the <nowiki> mark.
1329  $text = $this->getConverterLanguage()->convert( $text );
1330  }
1331  }
1332 
1333  $text = $this->mStripState->unstripNoWiki( $text );
1334 
1335  if ( $isMain ) {
1336  Hooks::run( 'ParserBeforeTidy', [ &$this, &$text ] );
1337  }
1338 
1339  $text = $this->replaceTransparentTags( $text );
1340  $text = $this->mStripState->unstripGeneral( $text );
1341 
1342  $text = Sanitizer::normalizeCharReferences( $text );
1343 
1344  if ( MWTidy::isEnabled() ) {
1345  if ( $this->mOptions->getTidy() ) {
1346  $text = MWTidy::tidy( $text );
1347  }
1348  } else {
1349  # attempt to sanitize at least some nesting problems
1350  # (bug #2702 and quite a few others)
1351  $tidyregs = [
1352  # ''Something [http://www.cool.com cool''] -->
1353  # <i>Something</i><a href="http://www.cool.com"..><i>cool></i></a>
1354  '/(<([bi])>)(<([bi])>)?([^<]*)(<\/?a[^<]*>)([^<]*)(<\/\\4>)?(<\/\\2>)/' =>
1355  '\\1\\3\\5\\8\\9\\6\\1\\3\\7\\8\\9',
1356  # fix up an anchor inside another anchor, only
1357  # at least for a single single nested link (bug 3695)
1358  '/(<a[^>]+>)([^<]*)(<a[^>]+>[^<]*)<\/a>(.*)<\/a>/' =>
1359  '\\1\\2</a>\\3</a>\\1\\4</a>',
1360  # fix div inside inline elements- doBlockLevels won't wrap a line which
1361  # contains a div, so fix it up here; replace
1362  # div with escaped text
1363  '/(<([aib]) [^>]+>)([^<]*)(<div([^>]*)>)(.*)(<\/div>)([^<]*)(<\/\\2>)/' =>
1364  '\\1\\3&lt;div\\5&gt;\\6&lt;/div&gt;\\8\\9',
1365  # remove empty italic or bold tag pairs, some
1366  # introduced by rules above
1367  '/<([bi])><\/\\1>/' => '',
1368  ];
1369 
1370  $text = preg_replace(
1371  array_keys( $tidyregs ),
1372  array_values( $tidyregs ),
1373  $text );
1374  }
1375 
1376  if ( $isMain ) {
1377  Hooks::run( 'ParserAfterTidy', [ &$this, &$text ] );
1378  }
1379 
1380  return $text;
1381  }
1382 
1394  public function doMagicLinks( $text ) {
1395  $prots = wfUrlProtocolsWithoutProtRel();
1396  $urlChar = self::EXT_LINK_URL_CLASS;
1397  $addr = self::EXT_LINK_ADDR;
1398  $space = self::SPACE_NOT_NL; # non-newline space
1399  $spdash = "(?:-|$space)"; # a dash or a non-newline space
1400  $spaces = "$space++"; # possessive match of 1 or more spaces
1401  $text = preg_replace_callback(
1402  '!(?: # Start cases
1403  (<a[ \t\r\n>].*?</a>) | # m[1]: Skip link text
1404  (<.*?>) | # m[2]: Skip stuff inside
1405  # HTML elements' . "
1406  (\b(?i:$prots)($addr$urlChar*)) | # m[3]: Free external links
1407  # m[4]: Post-protocol path
1408  \b(?:RFC|PMID) $spaces # m[5]: RFC or PMID, capture number
1409  ([0-9]+)\b |
1410  \bISBN $spaces ( # m[6]: ISBN, capture number
1411  (?: 97[89] $spdash? )? # optional 13-digit ISBN prefix
1412  (?: [0-9] $spdash? ){9} # 9 digits with opt. delimiters
1413  [0-9Xx] # check digit
1414  )\b
1415  )!xu", [ &$this, 'magicLinkCallback' ], $text );
1416  return $text;
1417  }
1418 
1424  public function magicLinkCallback( $m ) {
1425  if ( isset( $m[1] ) && $m[1] !== '' ) {
1426  # Skip anchor
1427  return $m[0];
1428  } elseif ( isset( $m[2] ) && $m[2] !== '' ) {
1429  # Skip HTML element
1430  return $m[0];
1431  } elseif ( isset( $m[3] ) && $m[3] !== '' ) {
1432  # Free external link
1433  return $this->makeFreeExternalLink( $m[0], strlen( $m[4] ) );
1434  } elseif ( isset( $m[5] ) && $m[5] !== '' ) {
1435  # RFC or PMID
1436  if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
1437  $keyword = 'RFC';
1438  $urlmsg = 'rfcurl';
1439  $cssClass = 'mw-magiclink-rfc';
1440  $id = $m[5];
1441  } elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) {
1442  $keyword = 'PMID';
1443  $urlmsg = 'pubmedurl';
1444  $cssClass = 'mw-magiclink-pmid';
1445  $id = $m[5];
1446  } else {
1447  throw new MWException( __METHOD__ . ': unrecognised match type "' .
1448  substr( $m[0], 0, 20 ) . '"' );
1449  }
1450  $url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
1451  return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $cssClass, [], $this->mTitle );
1452  } elseif ( isset( $m[6] ) && $m[6] !== '' ) {
1453  # ISBN
1454  $isbn = $m[6];
1455  $space = self::SPACE_NOT_NL; # non-newline space
1456  $isbn = preg_replace( "/$space/", ' ', $isbn );
1457  $num = strtr( $isbn, [
1458  '-' => '',
1459  ' ' => '',
1460  'x' => 'X',
1461  ] );
1462  return $this->getLinkRenderer()->makeKnownLink(
1463  SpecialPage::getTitleFor( 'Booksources', $num ),
1464  "ISBN $isbn",
1465  [
1466  'class' => 'internal mw-magiclink-isbn',
1467  'title' => false // suppress title attribute
1468  ]
1469  );
1470  } else {
1471  return $m[0];
1472  }
1473  }
1474 
1484  public function makeFreeExternalLink( $url, $numPostProto ) {
1485  $trail = '';
1486 
1487  # The characters '<' and '>' (which were escaped by
1488  # removeHTMLtags()) should not be included in
1489  # URLs, per RFC 2396.
1490  # Make &nbsp; terminate a URL as well (bug T84937)
1491  $m2 = [];
1492  if ( preg_match(
1493  '/&(lt|gt|nbsp|#x0*(3[CcEe]|[Aa]0)|#0*(60|62|160));/',
1494  $url,
1495  $m2,
1496  PREG_OFFSET_CAPTURE
1497  ) ) {
1498  $trail = substr( $url, $m2[0][1] ) . $trail;
1499  $url = substr( $url, 0, $m2[0][1] );
1500  }
1501 
1502  # Move trailing punctuation to $trail
1503  $sep = ',;\.:!?';
1504  # If there is no left bracket, then consider right brackets fair game too
1505  if ( strpos( $url, '(' ) === false ) {
1506  $sep .= ')';
1507  }
1508 
1509  $urlRev = strrev( $url );
1510  $numSepChars = strspn( $urlRev, $sep );
1511  # Don't break a trailing HTML entity by moving the ; into $trail
1512  # This is in hot code, so use substr_compare to avoid having to
1513  # create a new string object for the comparison
1514  if ( $numSepChars && substr_compare( $url, ";", -$numSepChars, 1 ) === 0 ) {
1515  # more optimization: instead of running preg_match with a $
1516  # anchor, which can be slow, do the match on the reversed
1517  # string starting at the desired offset.
1518  # un-reversed regexp is: /&([a-z]+|#x[\da-f]+|#\d+)$/i
1519  if ( preg_match( '/\G([a-z]+|[\da-f]+x#|\d+#)&/i', $urlRev, $m2, 0, $numSepChars ) ) {
1520  $numSepChars--;
1521  }
1522  }
1523  if ( $numSepChars ) {
1524  $trail = substr( $url, -$numSepChars ) . $trail;
1525  $url = substr( $url, 0, -$numSepChars );
1526  }
1527 
1528  # Verify that we still have a real URL after trail removal, and
1529  # not just lone protocol
1530  if ( strlen( $trail ) >= $numPostProto ) {
1531  return $url . $trail;
1532  }
1533 
1534  $url = Sanitizer::cleanUrl( $url );
1535 
1536  # Is this an external image?
1537  $text = $this->maybeMakeExternalImage( $url );
1538  if ( $text === false ) {
1539  # Not an image, make a link
1540  $text = Linker::makeExternalLink( $url,
1541  $this->getConverterLanguage()->markNoConversion( $url, true ),
1542  true, 'free',
1543  $this->getExternalLinkAttribs( $url ), $this->mTitle );
1544  # Register it in the output object...
1545  # Replace unnecessary URL escape codes with their equivalent characters
1546  $pasteurized = self::normalizeLinkUrl( $url );
1547  $this->mOutput->addExternalLink( $pasteurized );
1548  }
1549  return $text . $trail;
1550  }
1551 
1561  public function doHeadings( $text ) {
1562  for ( $i = 6; $i >= 1; --$i ) {
1563  $h = str_repeat( '=', $i );
1564  $text = preg_replace( "/^$h(.+)$h\\s*$/m", "<h$i>\\1</h$i>", $text );
1565  }
1566  return $text;
1567  }
1568 
1577  public function doAllQuotes( $text ) {
1578  $outtext = '';
1579  $lines = StringUtils::explode( "\n", $text );
1580  foreach ( $lines as $line ) {
1581  $outtext .= $this->doQuotes( $line ) . "\n";
1582  }
1583  $outtext = substr( $outtext, 0, -1 );
1584  return $outtext;
1585  }
1586 
1594  public function doQuotes( $text ) {
1595  $arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1596  $countarr = count( $arr );
1597  if ( $countarr == 1 ) {
1598  return $text;
1599  }
1600 
1601  // First, do some preliminary work. This may shift some apostrophes from
1602  // being mark-up to being text. It also counts the number of occurrences
1603  // of bold and italics mark-ups.
1604  $numbold = 0;
1605  $numitalics = 0;
1606  for ( $i = 1; $i < $countarr; $i += 2 ) {
1607  $thislen = strlen( $arr[$i] );
1608  // If there are ever four apostrophes, assume the first is supposed to
1609  // be text, and the remaining three constitute mark-up for bold text.
1610  // (bug 13227: ''''foo'''' turns into ' ''' foo ' ''')
1611  if ( $thislen == 4 ) {
1612  $arr[$i - 1] .= "'";
1613  $arr[$i] = "'''";
1614  $thislen = 3;
1615  } elseif ( $thislen > 5 ) {
1616  // If there are more than 5 apostrophes in a row, assume they're all
1617  // text except for the last 5.
1618  // (bug 13227: ''''''foo'''''' turns into ' ''''' foo ' ''''')
1619  $arr[$i - 1] .= str_repeat( "'", $thislen - 5 );
1620  $arr[$i] = "'''''";
1621  $thislen = 5;
1622  }
1623  // Count the number of occurrences of bold and italics mark-ups.
1624  if ( $thislen == 2 ) {
1625  $numitalics++;
1626  } elseif ( $thislen == 3 ) {
1627  $numbold++;
1628  } elseif ( $thislen == 5 ) {
1629  $numitalics++;
1630  $numbold++;
1631  }
1632  }
1633 
1634  // If there is an odd number of both bold and italics, it is likely
1635  // that one of the bold ones was meant to be an apostrophe followed
1636  // by italics. Which one we cannot know for certain, but it is more
1637  // likely to be one that has a single-letter word before it.
1638  if ( ( $numbold % 2 == 1 ) && ( $numitalics % 2 == 1 ) ) {
1639  $firstsingleletterword = -1;
1640  $firstmultiletterword = -1;
1641  $firstspace = -1;
1642  for ( $i = 1; $i < $countarr; $i += 2 ) {
1643  if ( strlen( $arr[$i] ) == 3 ) {
1644  $x1 = substr( $arr[$i - 1], -1 );
1645  $x2 = substr( $arr[$i - 1], -2, 1 );
1646  if ( $x1 === ' ' ) {
1647  if ( $firstspace == -1 ) {
1648  $firstspace = $i;
1649  }
1650  } elseif ( $x2 === ' ' ) {
1651  $firstsingleletterword = $i;
1652  // if $firstsingleletterword is set, we don't
1653  // look at the other options, so we can bail early.
1654  break;
1655  } else {
1656  if ( $firstmultiletterword == -1 ) {
1657  $firstmultiletterword = $i;
1658  }
1659  }
1660  }
1661  }
1662 
1663  // If there is a single-letter word, use it!
1664  if ( $firstsingleletterword > -1 ) {
1665  $arr[$firstsingleletterword] = "''";
1666  $arr[$firstsingleletterword - 1] .= "'";
1667  } elseif ( $firstmultiletterword > -1 ) {
1668  // If not, but there's a multi-letter word, use that one.
1669  $arr[$firstmultiletterword] = "''";
1670  $arr[$firstmultiletterword - 1] .= "'";
1671  } elseif ( $firstspace > -1 ) {
1672  // ... otherwise use the first one that has neither.
1673  // (notice that it is possible for all three to be -1 if, for example,
1674  // there is only one pentuple-apostrophe in the line)
1675  $arr[$firstspace] = "''";
1676  $arr[$firstspace - 1] .= "'";
1677  }
1678  }
1679 
1680  // Now let's actually convert our apostrophic mush to HTML!
1681  $output = '';
1682  $buffer = '';
1683  $state = '';
1684  $i = 0;
1685  foreach ( $arr as $r ) {
1686  if ( ( $i % 2 ) == 0 ) {
1687  if ( $state === 'both' ) {
1688  $buffer .= $r;
1689  } else {
1690  $output .= $r;
1691  }
1692  } else {
1693  $thislen = strlen( $r );
1694  if ( $thislen == 2 ) {
1695  if ( $state === 'i' ) {
1696  $output .= '</i>';
1697  $state = '';
1698  } elseif ( $state === 'bi' ) {
1699  $output .= '</i>';
1700  $state = 'b';
1701  } elseif ( $state === 'ib' ) {
1702  $output .= '</b></i><b>';
1703  $state = 'b';
1704  } elseif ( $state === 'both' ) {
1705  $output .= '<b><i>' . $buffer . '</i>';
1706  $state = 'b';
1707  } else { // $state can be 'b' or ''
1708  $output .= '<i>';
1709  $state .= 'i';
1710  }
1711  } elseif ( $thislen == 3 ) {
1712  if ( $state === 'b' ) {
1713  $output .= '</b>';
1714  $state = '';
1715  } elseif ( $state === 'bi' ) {
1716  $output .= '</i></b><i>';
1717  $state = 'i';
1718  } elseif ( $state === 'ib' ) {
1719  $output .= '</b>';
1720  $state = 'i';
1721  } elseif ( $state === 'both' ) {
1722  $output .= '<i><b>' . $buffer . '</b>';
1723  $state = 'i';
1724  } else { // $state can be 'i' or ''
1725  $output .= '<b>';
1726  $state .= 'b';
1727  }
1728  } elseif ( $thislen == 5 ) {
1729  if ( $state === 'b' ) {
1730  $output .= '</b><i>';
1731  $state = 'i';
1732  } elseif ( $state === 'i' ) {
1733  $output .= '</i><b>';
1734  $state = 'b';
1735  } elseif ( $state === 'bi' ) {
1736  $output .= '</i></b>';
1737  $state = '';
1738  } elseif ( $state === 'ib' ) {
1739  $output .= '</b></i>';
1740  $state = '';
1741  } elseif ( $state === 'both' ) {
1742  $output .= '<i><b>' . $buffer . '</b></i>';
1743  $state = '';
1744  } else { // ($state == '')
1745  $buffer = '';
1746  $state = 'both';
1747  }
1748  }
1749  }
1750  $i++;
1751  }
1752  // Now close all remaining tags. Notice that the order is important.
1753  if ( $state === 'b' || $state === 'ib' ) {
1754  $output .= '</b>';
1755  }
1756  if ( $state === 'i' || $state === 'bi' || $state === 'ib' ) {
1757  $output .= '</i>';
1758  }
1759  if ( $state === 'bi' ) {
1760  $output .= '</b>';
1761  }
1762  // There might be lonely ''''', so make sure we have a buffer
1763  if ( $state === 'both' && $buffer ) {
1764  $output .= '<b><i>' . $buffer . '</i></b>';
1765  }
1766  return $output;
1767  }
1768 
1782  public function replaceExternalLinks( $text ) {
1783 
1784  $bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
1785  if ( $bits === false ) {
1786  throw new MWException( "PCRE needs to be compiled with "
1787  . "--enable-unicode-properties in order for MediaWiki to function" );
1788  }
1789  $s = array_shift( $bits );
1790 
1791  $i = 0;
1792  while ( $i < count( $bits ) ) {
1793  $url = $bits[$i++];
1794  $i++; // protocol
1795  $text = $bits[$i++];
1796  $trail = $bits[$i++];
1797 
1798  # The characters '<' and '>' (which were escaped by
1799  # removeHTMLtags()) should not be included in
1800  # URLs, per RFC 2396.
1801  $m2 = [];
1802  if ( preg_match( '/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE ) ) {
1803  $text = substr( $url, $m2[0][1] ) . ' ' . $text;
1804  $url = substr( $url, 0, $m2[0][1] );
1805  }
1806 
1807  # If the link text is an image URL, replace it with an <img> tag
1808  # This happened by accident in the original parser, but some people used it extensively
1809  $img = $this->maybeMakeExternalImage( $text );
1810  if ( $img !== false ) {
1811  $text = $img;
1812  }
1813 
1814  $dtrail = '';
1815 
1816  # Set linktype for CSS - if URL==text, link is essentially free
1817  $linktype = ( $text === $url ) ? 'free' : 'text';
1818 
1819  # No link text, e.g. [http://domain.tld/some.link]
1820  if ( $text == '' ) {
1821  # Autonumber
1822  $langObj = $this->getTargetLanguage();
1823  $text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
1824  $linktype = 'autonumber';
1825  } else {
1826  # Have link text, e.g. [http://domain.tld/some.link text]s
1827  # Check for trail
1828  list( $dtrail, $trail ) = Linker::splitTrail( $trail );
1829  }
1830 
1831  $text = $this->getConverterLanguage()->markNoConversion( $text );
1832 
1833  $url = Sanitizer::cleanUrl( $url );
1834 
1835  # Use the encoded URL
1836  # This means that users can paste URLs directly into the text
1837  # Funny characters like ö aren't valid in URLs anyway
1838  # This was changed in August 2004
1839  $s .= Linker::makeExternalLink( $url, $text, false, $linktype,
1840  $this->getExternalLinkAttribs( $url ), $this->mTitle ) . $dtrail . $trail;
1841 
1842  # Register link in the output object.
1843  # Replace unnecessary URL escape codes with the referenced character
1844  # This prevents spammers from hiding links from the filters
1845  $pasteurized = self::normalizeLinkUrl( $url );
1846  $this->mOutput->addExternalLink( $pasteurized );
1847  }
1848 
1849  return $s;
1850  }
1851 
1861  public static function getExternalLinkRel( $url = false, $title = null ) {
1863  $ns = $title ? $title->getNamespace() : false;
1864  if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions )
1865  && !wfMatchesDomainList( $url, $wgNoFollowDomainExceptions )
1866  ) {
1867  return 'nofollow';
1868  }
1869  return null;
1870  }
1871 
1882  public function getExternalLinkAttribs( $url ) {
1883  $attribs = [];
1884  $rel = self::getExternalLinkRel( $url, $this->mTitle );
1885 
1886  $target = $this->mOptions->getExternalLinkTarget();
1887  if ( $target ) {
1888  $attribs['target'] = $target;
1889  if ( !in_array( $target, [ '_self', '_parent', '_top' ] ) ) {
1890  // T133507. New windows can navigate parent cross-origin.
1891  // Including noreferrer due to lacking browser
1892  // support of noopener. Eventually noreferrer should be removed.
1893  if ( $rel !== '' ) {
1894  $rel .= ' ';
1895  }
1896  $rel .= 'noreferrer noopener';
1897  }
1898  }
1899  $attribs['rel'] = $rel;
1900  return $attribs;
1901  }
1902 
1910  public static function replaceUnusualEscapes( $url ) {
1911  wfDeprecated( __METHOD__, '1.24' );
1912  return self::normalizeLinkUrl( $url );
1913  }
1914 
1924  public static function normalizeLinkUrl( $url ) {
1925  # First, make sure unsafe characters are encoded
1926  $url = preg_replace_callback( '/[\x00-\x20"<>\[\\\\\]^`{|}\x7F-\xFF]/',
1927  function ( $m ) {
1928  return rawurlencode( $m[0] );
1929  },
1930  $url
1931  );
1932 
1933  $ret = '';
1934  $end = strlen( $url );
1935 
1936  # Fragment part - 'fragment'
1937  $start = strpos( $url, '#' );
1938  if ( $start !== false && $start < $end ) {
1939  $ret = self::normalizeUrlComponent(
1940  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}' ) . $ret;
1941  $end = $start;
1942  }
1943 
1944  # Query part - 'query' minus &=+;
1945  $start = strpos( $url, '?' );
1946  if ( $start !== false && $start < $end ) {
1947  $ret = self::normalizeUrlComponent(
1948  substr( $url, $start, $end - $start ), '"#%<>[\]^`{|}&=+;' ) . $ret;
1949  $end = $start;
1950  }
1951 
1952  # Scheme and path part - 'pchar'
1953  # (we assume no userinfo or encoded colons in the host)
1954  $ret = self::normalizeUrlComponent(
1955  substr( $url, 0, $end ), '"#%<>[\]^`{|}/?' ) . $ret;
1956 
1957  return $ret;
1958  }
1959 
1960  private static function normalizeUrlComponent( $component, $unsafe ) {
1961  $callback = function ( $matches ) use ( $unsafe ) {
1962  $char = urldecode( $matches[0] );
1963  $ord = ord( $char );
1964  if ( $ord > 32 && $ord < 127 && strpos( $unsafe, $char ) === false ) {
1965  # Unescape it
1966  return $char;
1967  } else {
1968  # Leave it escaped, but use uppercase for a-f
1969  return strtoupper( $matches[0] );
1970  }
1971  };
1972  return preg_replace_callback( '/%[0-9A-Fa-f]{2}/', $callback, $component );
1973  }
1974 
1983  private function maybeMakeExternalImage( $url ) {
1984  $imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
1985  $imagesexception = !empty( $imagesfrom );
1986  $text = false;
1987  # $imagesfrom could be either a single string or an array of strings, parse out the latter
1988  if ( $imagesexception && is_array( $imagesfrom ) ) {
1989  $imagematch = false;
1990  foreach ( $imagesfrom as $match ) {
1991  if ( strpos( $url, $match ) === 0 ) {
1992  $imagematch = true;
1993  break;
1994  }
1995  }
1996  } elseif ( $imagesexception ) {
1997  $imagematch = ( strpos( $url, $imagesfrom ) === 0 );
1998  } else {
1999  $imagematch = false;
2000  }
2001 
2002  if ( $this->mOptions->getAllowExternalImages()
2003  || ( $imagesexception && $imagematch )
2004  ) {
2005  if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
2006  # Image found
2007  $text = Linker::makeExternalImage( $url );
2008  }
2009  }
2010  if ( !$text && $this->mOptions->getEnableImageWhitelist()
2011  && preg_match( self::EXT_IMAGE_REGEX, $url )
2012  ) {
2013  $whitelist = explode(
2014  "\n",
2015  wfMessage( 'external_image_whitelist' )->inContentLanguage()->text()
2016  );
2017 
2018  foreach ( $whitelist as $entry ) {
2019  # Sanitize the regex fragment, make it case-insensitive, ignore blank entries/comments
2020  if ( strpos( $entry, '#' ) === 0 || $entry === '' ) {
2021  continue;
2022  }
2023  if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
2024  # Image matches a whitelist entry
2025  $text = Linker::makeExternalImage( $url );
2026  break;
2027  }
2028  }
2029  }
2030  return $text;
2031  }
2032 
2042  public function replaceInternalLinks( $s ) {
2043  $this->mLinkHolders->merge( $this->replaceInternalLinks2( $s ) );
2044  return $s;
2045  }
2046 
2055  public function replaceInternalLinks2( &$s ) {
2057 
2058  static $tc = false, $e1, $e1_img;
2059  # the % is needed to support urlencoded titles as well
2060  if ( !$tc ) {
2061  $tc = Title::legalChars() . '#%';
2062  # Match a link having the form [[namespace:link|alternate]]trail
2063  $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
2064  # Match cases where there is no "]]", which might still be images
2065  $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
2066  }
2067 
2068  $holders = new LinkHolderArray( $this );
2069 
2070  # split the entire text string on occurrences of [[
2071  $a = StringUtils::explode( '[[', ' ' . $s );
2072  # get the first element (all text up to first [[), and remove the space we added
2073  $s = $a->current();
2074  $a->next();
2075  $line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
2076  $s = substr( $s, 1 );
2077 
2078  $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
2079  $e2 = null;
2080  if ( $useLinkPrefixExtension ) {
2081  # Match the end of a line for a word that's not followed by whitespace,
2082  # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
2084  $charset = $wgContLang->linkPrefixCharset();
2085  $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
2086  }
2087 
2088  if ( is_null( $this->mTitle ) ) {
2089  throw new MWException( __METHOD__ . ": \$this->mTitle is null\n" );
2090  }
2091  $nottalk = !$this->mTitle->isTalkPage();
2092 
2093  if ( $useLinkPrefixExtension ) {
2094  $m = [];
2095  if ( preg_match( $e2, $s, $m ) ) {
2096  $first_prefix = $m[2];
2097  } else {
2098  $first_prefix = false;
2099  }
2100  } else {
2101  $prefix = '';
2102  }
2103 
2104  $useSubpages = $this->areSubpagesAllowed();
2105 
2106  // @codingStandardsIgnoreStart Squiz.WhiteSpace.SemicolonSpacing.Incorrect
2107  # Loop for each link
2108  for ( ; $line !== false && $line !== null; $a->next(), $line = $a->current() ) {
2109  // @codingStandardsIgnoreEnd
2110 
2111  # Check for excessive memory usage
2112  if ( $holders->isBig() ) {
2113  # Too big
2114  # Do the existence check, replace the link holders and clear the array
2115  $holders->replace( $s );
2116  $holders->clear();
2117  }
2118 
2119  if ( $useLinkPrefixExtension ) {
2120  if ( preg_match( $e2, $s, $m ) ) {
2121  $prefix = $m[2];
2122  $s = $m[1];
2123  } else {
2124  $prefix = '';
2125  }
2126  # first link
2127  if ( $first_prefix ) {
2128  $prefix = $first_prefix;
2129  $first_prefix = false;
2130  }
2131  }
2132 
2133  $might_be_img = false;
2134 
2135  if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
2136  $text = $m[2];
2137  # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
2138  # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
2139  # the real problem is with the $e1 regex
2140  # See bug 1300.
2141  # Still some problems for cases where the ] is meant to be outside punctuation,
2142  # and no image is in sight. See bug 2095.
2143  if ( $text !== ''
2144  && substr( $m[3], 0, 1 ) === ']'
2145  && strpos( $text, '[' ) !== false
2146  ) {
2147  $text .= ']'; # so that replaceExternalLinks($text) works later
2148  $m[3] = substr( $m[3], 1 );
2149  }
2150  # fix up urlencoded title texts
2151  if ( strpos( $m[1], '%' ) !== false ) {
2152  # Should anchors '#' also be rejected?
2153  $m[1] = str_replace( [ '<', '>' ], [ '&lt;', '&gt;' ], rawurldecode( $m[1] ) );
2154  }
2155  $trail = $m[3];
2156  } elseif ( preg_match( $e1_img, $line, $m ) ) {
2157  # Invalid, but might be an image with a link in its caption
2158  $might_be_img = true;
2159  $text = $m[2];
2160  if ( strpos( $m[1], '%' ) !== false ) {
2161  $m[1] = rawurldecode( $m[1] );
2162  }
2163  $trail = "";
2164  } else { # Invalid form; output directly
2165  $s .= $prefix . '[[' . $line;
2166  continue;
2167  }
2168 
2169  $origLink = $m[1];
2170 
2171  # Don't allow internal links to pages containing
2172  # PROTO: where PROTO is a valid URL protocol; these
2173  # should be external links.
2174  if ( preg_match( '/^(?i:' . $this->mUrlProtocols . ')/', $origLink ) ) {
2175  $s .= $prefix . '[[' . $line;
2176  continue;
2177  }
2178 
2179  # Make subpage if necessary
2180  if ( $useSubpages ) {
2181  $link = $this->maybeDoSubpageLink( $origLink, $text );
2182  } else {
2183  $link = $origLink;
2184  }
2185 
2186  $noforce = ( substr( $origLink, 0, 1 ) !== ':' );
2187  if ( !$noforce ) {
2188  # Strip off leading ':'
2189  $link = substr( $link, 1 );
2190  }
2191 
2192  $unstrip = $this->mStripState->unstripNoWiki( $link );
2193  $nt = is_string( $unstrip ) ? Title::newFromText( $unstrip ) : null;
2194  if ( $nt === null ) {
2195  $s .= $prefix . '[[' . $line;
2196  continue;
2197  }
2198 
2199  $ns = $nt->getNamespace();
2200  $iw = $nt->getInterwiki();
2201 
2202  if ( $might_be_img ) { # if this is actually an invalid link
2203  if ( $ns == NS_FILE && $noforce ) { # but might be an image
2204  $found = false;
2205  while ( true ) {
2206  # look at the next 'line' to see if we can close it there
2207  $a->next();
2208  $next_line = $a->current();
2209  if ( $next_line === false || $next_line === null ) {
2210  break;
2211  }
2212  $m = explode( ']]', $next_line, 3 );
2213  if ( count( $m ) == 3 ) {
2214  # the first ]] closes the inner link, the second the image
2215  $found = true;
2216  $text .= "[[{$m[0]}]]{$m[1]}";
2217  $trail = $m[2];
2218  break;
2219  } elseif ( count( $m ) == 2 ) {
2220  # if there's exactly one ]] that's fine, we'll keep looking
2221  $text .= "[[{$m[0]}]]{$m[1]}";
2222  } else {
2223  # if $next_line is invalid too, we need look no further
2224  $text .= '[[' . $next_line;
2225  break;
2226  }
2227  }
2228  if ( !$found ) {
2229  # we couldn't find the end of this imageLink, so output it raw
2230  # but don't ignore what might be perfectly normal links in the text we've examined
2231  $holders->merge( $this->replaceInternalLinks2( $text ) );
2232  $s .= "{$prefix}[[$link|$text";
2233  # note: no $trail, because without an end, there *is* no trail
2234  continue;
2235  }
2236  } else { # it's not an image, so output it raw
2237  $s .= "{$prefix}[[$link|$text";
2238  # note: no $trail, because without an end, there *is* no trail
2239  continue;
2240  }
2241  }
2242 
2243  $wasblank = ( $text == '' );
2244  if ( $wasblank ) {
2245  $text = $link;
2246  } else {
2247  # Bug 4598 madness. Handle the quotes only if they come from the alternate part
2248  # [[Lista d''e paise d''o munno]] -> <a href="...">Lista d''e paise d''o munno</a>
2249  # [[Criticism of Harry Potter|Criticism of ''Harry Potter'']]
2250  # -> <a href="Criticism of Harry Potter">Criticism of <i>Harry Potter</i></a>
2251  $text = $this->doQuotes( $text );
2252  }
2253 
2254  # Link not escaped by : , create the various objects
2255  if ( $noforce && !$nt->wasLocalInterwiki() ) {
2256  # Interwikis
2257  if (
2258  $iw && $this->mOptions->getInterwikiMagic() && $nottalk && (
2259  Language::fetchLanguageName( $iw, null, 'mw' ) ||
2260  in_array( $iw, $wgExtraInterlanguageLinkPrefixes )
2261  )
2262  ) {
2263  # Bug 24502: filter duplicates
2264  if ( !isset( $this->mLangLinkLanguages[$iw] ) ) {
2265  $this->mLangLinkLanguages[$iw] = true;
2266  $this->mOutput->addLanguageLink( $nt->getFullText() );
2267  }
2268 
2269  $s = rtrim( $s . $prefix );
2270  $s .= trim( $trail, "\n" ) == '' ? '': $prefix . $trail;
2271  continue;
2272  }
2273 
2274  if ( $ns == NS_FILE ) {
2275  if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
2276  if ( $wasblank ) {
2277  # if no parameters were passed, $text
2278  # becomes something like "File:Foo.png",
2279  # which we don't want to pass on to the
2280  # image generator
2281  $text = '';
2282  } else {
2283  # recursively parse links inside the image caption
2284  # actually, this will parse them in any other parameters, too,
2285  # but it might be hard to fix that, and it doesn't matter ATM
2286  $text = $this->replaceExternalLinks( $text );
2287  $holders->merge( $this->replaceInternalLinks2( $text ) );
2288  }
2289  # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
2290  $s .= $prefix . $this->armorLinks(
2291  $this->makeImage( $nt, $text, $holders ) ) . $trail;
2292  continue;
2293  }
2294  } elseif ( $ns == NS_CATEGORY ) {
2295  $s = rtrim( $s . "\n" ); # bug 87
2296 
2297  if ( $wasblank ) {
2298  $sortkey = $this->getDefaultSort();
2299  } else {
2300  $sortkey = $text;
2301  }
2302  $sortkey = Sanitizer::decodeCharReferences( $sortkey );
2303  $sortkey = str_replace( "\n", '', $sortkey );
2304  $sortkey = $this->getConverterLanguage()->convertCategoryKey( $sortkey );
2305  $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
2306 
2310  $s .= trim( $prefix . $trail, "\n" ) == '' ? '' : $prefix . $trail;
2311 
2312  continue;
2313  }
2314  }
2315 
2316  # Self-link checking. For some languages, variants of the title are checked in
2317  # LinkHolderArray::doVariants() to allow batching the existence checks necessary
2318  # for linking to a different variant.
2319  if ( $ns != NS_SPECIAL && $nt->equals( $this->mTitle ) && !$nt->hasFragment() ) {
2320  $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail );
2321  continue;
2322  }
2323 
2324  # NS_MEDIA is a pseudo-namespace for linking directly to a file
2325  # @todo FIXME: Should do batch file existence checks, see comment below
2326  if ( $ns == NS_MEDIA ) {
2327  # Give extensions a chance to select the file revision for us
2328  $options = [];
2329  $descQuery = false;
2330  Hooks::run( 'BeforeParserFetchFileAndTitle',
2331  [ $this, $nt, &$options, &$descQuery ] );
2332  # Fetch and register the file (file title may be different via hooks)
2333  list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $options );
2334  # Cloak with NOPARSE to avoid replacement in replaceExternalLinks
2335  $s .= $prefix . $this->armorLinks(
2336  Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
2337  continue;
2338  }
2339 
2340  # Some titles, such as valid special pages or files in foreign repos, should
2341  # be shown as bluelinks even though they're not included in the page table
2342  # @todo FIXME: isAlwaysKnown() can be expensive for file links; we should really do
2343  # batch file existence checks for NS_FILE and NS_MEDIA
2344  if ( $iw == '' && $nt->isAlwaysKnown() ) {
2345  $this->mOutput->addLink( $nt );
2346  $s .= $this->makeKnownLinkHolder( $nt, $text, $trail, $prefix );
2347  } else {
2348  # Links will be added to the output link list after checking
2349  $s .= $holders->makeHolder( $nt, $text, [], $trail, $prefix );
2350  }
2351  }
2352  return $holders;
2353  }
2354 
2368  protected function makeKnownLinkHolder( $nt, $text = '', $trail = '', $prefix = '' ) {
2369  list( $inside, $trail ) = Linker::splitTrail( $trail );
2370 
2371  if ( $text == '' ) {
2372  $text = htmlspecialchars( $nt->getPrefixedText() );
2373  }
2374 
2375  $link = $this->getLinkRenderer()->makeKnownLink(
2376  $nt, new HtmlArmor( "$prefix$text$inside" )
2377  );
2378 
2379  return $this->armorLinks( $link ) . $trail;
2380  }
2381 
2392  public function armorLinks( $text ) {
2393  return preg_replace( '/\b((?i)' . $this->mUrlProtocols . ')/',
2394  self::MARKER_PREFIX . "NOPARSE$1", $text );
2395  }
2396 
2401  public function areSubpagesAllowed() {
2402  # Some namespaces don't allow subpages
2403  return MWNamespace::hasSubpages( $this->mTitle->getNamespace() );
2404  }
2405 
2414  public function maybeDoSubpageLink( $target, &$text ) {
2415  return Linker::normalizeSubpageLink( $this->mTitle, $target, $text );
2416  }
2417 
2426  public function doBlockLevels( $text, $linestart ) {
2427  return BlockLevelPass::doBlockLevels( $text, $linestart );
2428  }
2429 
2441  public function getVariableValue( $index, $frame = false ) {
2444 
2445  if ( is_null( $this->mTitle ) ) {
2446  // If no title set, bad things are going to happen
2447  // later. Title should always be set since this
2448  // should only be called in the middle of a parse
2449  // operation (but the unit-tests do funky stuff)
2450  throw new MWException( __METHOD__ . ' Should only be '
2451  . ' called while parsing (no title set)' );
2452  }
2453 
2458  if ( Hooks::run( 'ParserGetVariableValueVarCache', [ &$this, &$this->mVarCache ] ) ) {
2459  if ( isset( $this->mVarCache[$index] ) ) {
2460  return $this->mVarCache[$index];
2461  }
2462  }
2463 
2464  $ts = wfTimestamp( TS_UNIX, $this->mOptions->getTimestamp() );
2465  Hooks::run( 'ParserGetVariableValueTs', [ &$this, &$ts ] );
2466 
2467  $pageLang = $this->getFunctionLang();
2468 
2469  switch ( $index ) {
2470  case '!':
2471  $value = '|';
2472  break;
2473  case 'currentmonth':
2474  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'm' ) );
2475  break;
2476  case 'currentmonth1':
2477  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2478  break;
2479  case 'currentmonthname':
2480  $value = $pageLang->getMonthName( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2481  break;
2482  case 'currentmonthnamegen':
2483  $value = $pageLang->getMonthNameGen( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2484  break;
2485  case 'currentmonthabbrev':
2486  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getInstance( $ts )->format( 'n' ) );
2487  break;
2488  case 'currentday':
2489  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'j' ) );
2490  break;
2491  case 'currentday2':
2492  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'd' ) );
2493  break;
2494  case 'localmonth':
2495  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'm' ) );
2496  break;
2497  case 'localmonth1':
2498  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2499  break;
2500  case 'localmonthname':
2501  $value = $pageLang->getMonthName( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2502  break;
2503  case 'localmonthnamegen':
2504  $value = $pageLang->getMonthNameGen( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2505  break;
2506  case 'localmonthabbrev':
2507  $value = $pageLang->getMonthAbbreviation( MWTimestamp::getLocalInstance( $ts )->format( 'n' ) );
2508  break;
2509  case 'localday':
2510  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'j' ) );
2511  break;
2512  case 'localday2':
2513  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'd' ) );
2514  break;
2515  case 'pagename':
2516  $value = wfEscapeWikiText( $this->mTitle->getText() );
2517  break;
2518  case 'pagenamee':
2519  $value = wfEscapeWikiText( $this->mTitle->getPartialURL() );
2520  break;
2521  case 'fullpagename':
2522  $value = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
2523  break;
2524  case 'fullpagenamee':
2525  $value = wfEscapeWikiText( $this->mTitle->getPrefixedURL() );
2526  break;
2527  case 'subpagename':
2528  $value = wfEscapeWikiText( $this->mTitle->getSubpageText() );
2529  break;
2530  case 'subpagenamee':
2531  $value = wfEscapeWikiText( $this->mTitle->getSubpageUrlForm() );
2532  break;
2533  case 'rootpagename':
2534  $value = wfEscapeWikiText( $this->mTitle->getRootText() );
2535  break;
2536  case 'rootpagenamee':
2537  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2538  ' ',
2539  '_',
2540  $this->mTitle->getRootText()
2541  ) ) );
2542  break;
2543  case 'basepagename':
2544  $value = wfEscapeWikiText( $this->mTitle->getBaseText() );
2545  break;
2546  case 'basepagenamee':
2547  $value = wfEscapeWikiText( wfUrlencode( str_replace(
2548  ' ',
2549  '_',
2550  $this->mTitle->getBaseText()
2551  ) ) );
2552  break;
2553  case 'talkpagename':
2554  if ( $this->mTitle->canTalk() ) {
2555  $talkPage = $this->mTitle->getTalkPage();
2556  $value = wfEscapeWikiText( $talkPage->getPrefixedText() );
2557  } else {
2558  $value = '';
2559  }
2560  break;
2561  case 'talkpagenamee':
2562  if ( $this->mTitle->canTalk() ) {
2563  $talkPage = $this->mTitle->getTalkPage();
2564  $value = wfEscapeWikiText( $talkPage->getPrefixedURL() );
2565  } else {
2566  $value = '';
2567  }
2568  break;
2569  case 'subjectpagename':
2570  $subjPage = $this->mTitle->getSubjectPage();
2571  $value = wfEscapeWikiText( $subjPage->getPrefixedText() );
2572  break;
2573  case 'subjectpagenamee':
2574  $subjPage = $this->mTitle->getSubjectPage();
2575  $value = wfEscapeWikiText( $subjPage->getPrefixedURL() );
2576  break;
2577  case 'pageid': // requested in bug 23427
2578  $pageid = $this->getTitle()->getArticleID();
2579  if ( $pageid == 0 ) {
2580  # 0 means the page doesn't exist in the database,
2581  # which means the user is previewing a new page.
2582  # The vary-revision flag must be set, because the magic word
2583  # will have a different value once the page is saved.
2584  $this->mOutput->setFlag( 'vary-revision' );
2585  wfDebug( __METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision...\n" );
2586  }
2587  $value = $pageid ? $pageid : null;
2588  break;
2589  case 'revisionid':
2590  # Let the edit saving system know we should parse the page
2591  # *after* a revision ID has been assigned.
2592  $this->mOutput->setFlag( 'vary-revision-id' );
2593  wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id...\n" );
2594  $value = $this->mRevisionId;
2595  if ( !$value && $this->mOptions->getSpeculativeRevIdCallback() ) {
2596  $value = call_user_func( $this->mOptions->getSpeculativeRevIdCallback() );
2597  $this->mOutput->setSpeculativeRevIdUsed( $value );
2598  }
2599  break;
2600  case 'revisionday':
2601  # Let the edit saving system know we should parse the page
2602  # *after* a revision ID has been assigned. This is for null edits.
2603  $this->mOutput->setFlag( 'vary-revision' );
2604  wfDebug( __METHOD__ . ": {{REVISIONDAY}} used, setting vary-revision...\n" );
2605  $value = intval( substr( $this->getRevisionTimestamp(), 6, 2 ) );
2606  break;
2607  case 'revisionday2':
2608  # Let the edit saving system know we should parse the page
2609  # *after* a revision ID has been assigned. This is for null edits.
2610  $this->mOutput->setFlag( 'vary-revision' );
2611  wfDebug( __METHOD__ . ": {{REVISIONDAY2}} used, setting vary-revision...\n" );
2612  $value = substr( $this->getRevisionTimestamp(), 6, 2 );
2613  break;
2614  case 'revisionmonth':
2615  # Let the edit saving system know we should parse the page
2616  # *after* a revision ID has been assigned. This is for null edits.
2617  $this->mOutput->setFlag( 'vary-revision' );
2618  wfDebug( __METHOD__ . ": {{REVISIONMONTH}} used, setting vary-revision...\n" );
2619  $value = substr( $this->getRevisionTimestamp(), 4, 2 );
2620  break;
2621  case 'revisionmonth1':
2622  # Let the edit saving system know we should parse the page
2623  # *after* a revision ID has been assigned. This is for null edits.
2624  $this->mOutput->setFlag( 'vary-revision' );
2625  wfDebug( __METHOD__ . ": {{REVISIONMONTH1}} used, setting vary-revision...\n" );
2626  $value = intval( substr( $this->getRevisionTimestamp(), 4, 2 ) );
2627  break;
2628  case 'revisionyear':
2629  # Let the edit saving system know we should parse the page
2630  # *after* a revision ID has been assigned. This is for null edits.
2631  $this->mOutput->setFlag( 'vary-revision' );
2632  wfDebug( __METHOD__ . ": {{REVISIONYEAR}} used, setting vary-revision...\n" );
2633  $value = substr( $this->getRevisionTimestamp(), 0, 4 );
2634  break;
2635  case 'revisiontimestamp':
2636  # Let the edit saving system know we should parse the page
2637  # *after* a revision ID has been assigned. This is for null edits.
2638  $this->mOutput->setFlag( 'vary-revision' );
2639  wfDebug( __METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" );
2640  $value = $this->getRevisionTimestamp();
2641  break;
2642  case 'revisionuser':
2643  # Let the edit saving system know we should parse the page
2644  # *after* a revision ID has been assigned for null edits.
2645  $this->mOutput->setFlag( 'vary-user' );
2646  wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-user...\n" );
2647  $value = $this->getRevisionUser();
2648  break;
2649  case 'revisionsize':
2650  $value = $this->getRevisionSize();
2651  break;
2652  case 'namespace':
2653  $value = str_replace( '_', ' ', $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2654  break;
2655  case 'namespacee':
2656  $value = wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
2657  break;
2658  case 'namespacenumber':
2659  $value = $this->mTitle->getNamespace();
2660  break;
2661  case 'talkspace':
2662  $value = $this->mTitle->canTalk()
2663  ? str_replace( '_', ' ', $this->mTitle->getTalkNsText() )
2664  : '';
2665  break;
2666  case 'talkspacee':
2667  $value = $this->mTitle->canTalk() ? wfUrlencode( $this->mTitle->getTalkNsText() ) : '';
2668  break;
2669  case 'subjectspace':
2670  $value = str_replace( '_', ' ', $this->mTitle->getSubjectNsText() );
2671  break;
2672  case 'subjectspacee':
2673  $value = ( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
2674  break;
2675  case 'currentdayname':
2676  $value = $pageLang->getWeekdayName( (int)MWTimestamp::getInstance( $ts )->format( 'w' ) + 1 );
2677  break;
2678  case 'currentyear':
2679  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'Y' ), true );
2680  break;
2681  case 'currenttime':
2682  $value = $pageLang->time( wfTimestamp( TS_MW, $ts ), false, false );
2683  break;
2684  case 'currenthour':
2685  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'H' ), true );
2686  break;
2687  case 'currentweek':
2688  # @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
2689  # int to remove the padding
2690  $value = $pageLang->formatNum( (int)MWTimestamp::getInstance( $ts )->format( 'W' ) );
2691  break;
2692  case 'currentdow':
2693  $value = $pageLang->formatNum( MWTimestamp::getInstance( $ts )->format( 'w' ) );
2694  break;
2695  case 'localdayname':
2696  $value = $pageLang->getWeekdayName(
2697  (int)MWTimestamp::getLocalInstance( $ts )->format( 'w' ) + 1
2698  );
2699  break;
2700  case 'localyear':
2701  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'Y' ), true );
2702  break;
2703  case 'localtime':
2704  $value = $pageLang->time(
2705  MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' ),
2706  false,
2707  false
2708  );
2709  break;
2710  case 'localhour':
2711  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'H' ), true );
2712  break;
2713  case 'localweek':
2714  # @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
2715  # int to remove the padding
2716  $value = $pageLang->formatNum( (int)MWTimestamp::getLocalInstance( $ts )->format( 'W' ) );
2717  break;
2718  case 'localdow':
2719  $value = $pageLang->formatNum( MWTimestamp::getLocalInstance( $ts )->format( 'w' ) );
2720  break;
2721  case 'numberofarticles':
2722  $value = $pageLang->formatNum( SiteStats::articles() );
2723  break;
2724  case 'numberoffiles':
2725  $value = $pageLang->formatNum( SiteStats::images() );
2726  break;
2727  case 'numberofusers':
2728  $value = $pageLang->formatNum( SiteStats::users() );
2729  break;
2730  case 'numberofactiveusers':
2731  $value = $pageLang->formatNum( SiteStats::activeUsers() );
2732  break;
2733  case 'numberofpages':
2734  $value = $pageLang->formatNum( SiteStats::pages() );
2735  break;
2736  case 'numberofadmins':
2737  $value = $pageLang->formatNum( SiteStats::numberingroup( 'sysop' ) );
2738  break;
2739  case 'numberofedits':
2740  $value = $pageLang->formatNum( SiteStats::edits() );
2741  break;
2742  case 'currenttimestamp':
2743  $value = wfTimestamp( TS_MW, $ts );
2744  break;
2745  case 'localtimestamp':
2746  $value = MWTimestamp::getLocalInstance( $ts )->format( 'YmdHis' );
2747  break;
2748  case 'currentversion':
2750  break;
2751  case 'articlepath':
2752  return $wgArticlePath;
2753  case 'sitename':
2754  return $wgSitename;
2755  case 'server':
2756  return $wgServer;
2757  case 'servername':
2758  return $wgServerName;
2759  case 'scriptpath':
2760  return $wgScriptPath;
2761  case 'stylepath':
2762  return $wgStylePath;
2763  case 'directionmark':
2764  return $pageLang->getDirMark();
2765  case 'contentlanguage':
2767  return $wgLanguageCode;
2768  case 'cascadingsources':
2770  break;
2771  default:
2772  $ret = null;
2773  Hooks::run(
2774  'ParserGetVariableValueSwitch',
2775  [ &$this, &$this->mVarCache, &$index, &$ret, &$frame ]
2776  );
2777 
2778  return $ret;
2779  }
2780 
2781  if ( $index ) {
2782  $this->mVarCache[$index] = $value;
2783  }
2784 
2785  return $value;
2786  }
2787 
2793  public function initialiseVariables() {
2794  $variableIDs = MagicWord::getVariableIDs();
2795  $substIDs = MagicWord::getSubstIDs();
2796 
2797  $this->mVariables = new MagicWordArray( $variableIDs );
2798  $this->mSubstWords = new MagicWordArray( $substIDs );
2799  }
2800 
2823  public function preprocessToDom( $text, $flags = 0 ) {
2824  $dom = $this->getPreprocessor()->preprocessToObj( $text, $flags );
2825  return $dom;
2826  }
2827 
2835  public static function splitWhitespace( $s ) {
2836  $ltrimmed = ltrim( $s );
2837  $w1 = substr( $s, 0, strlen( $s ) - strlen( $ltrimmed ) );
2838  $trimmed = rtrim( $ltrimmed );
2839  $diff = strlen( $ltrimmed ) - strlen( $trimmed );
2840  if ( $diff > 0 ) {
2841  $w2 = substr( $ltrimmed, -$diff );
2842  } else {
2843  $w2 = '';
2844  }
2845  return [ $w1, $trimmed, $w2 ];
2846  }
2847 
2868  public function replaceVariables( $text, $frame = false, $argsOnly = false ) {
2869  # Is there any text? Also, Prevent too big inclusions!
2870  $textSize = strlen( $text );
2871  if ( $textSize < 1 || $textSize > $this->mOptions->getMaxIncludeSize() ) {
2872  return $text;
2873  }
2874 
2875  if ( $frame === false ) {
2876  $frame = $this->getPreprocessor()->newFrame();
2877  } elseif ( !( $frame instanceof PPFrame ) ) {
2878  wfDebug( __METHOD__ . " called using plain parameters instead of "
2879  . "a PPFrame instance. Creating custom frame.\n" );
2880  $frame = $this->getPreprocessor()->newCustomFrame( $frame );
2881  }
2882 
2883  $dom = $this->preprocessToDom( $text );
2884  $flags = $argsOnly ? PPFrame::NO_TEMPLATES : 0;
2885  $text = $frame->expand( $dom, $flags );
2886 
2887  return $text;
2888  }
2889 
2897  public static function createAssocArgs( $args ) {
2898  $assocArgs = [];
2899  $index = 1;
2900  foreach ( $args as $arg ) {
2901  $eqpos = strpos( $arg, '=' );
2902  if ( $eqpos === false ) {
2903  $assocArgs[$index++] = $arg;
2904  } else {
2905  $name = trim( substr( $arg, 0, $eqpos ) );
2906  $value = trim( substr( $arg, $eqpos + 1 ) );
2907  if ( $value === false ) {
2908  $value = '';
2909  }
2910  if ( $name !== false ) {
2911  $assocArgs[$name] = $value;
2912  }
2913  }
2914  }
2915 
2916  return $assocArgs;
2917  }
2918 
2945  public function limitationWarn( $limitationType, $current = '', $max = '' ) {
2946  # does no harm if $current and $max are present but are unnecessary for the message
2947  # Not doing ->inLanguage( $this->mOptions->getUserLangObj() ), since this is shown
2948  # only during preview, and that would split the parser cache unnecessarily.
2949  $warning = wfMessage( "$limitationType-warning" )->numParams( $current, $max )
2950  ->text();
2951  $this->mOutput->addWarning( $warning );
2952  $this->addTrackingCategory( "$limitationType-category" );
2953  }
2954 
2967  public function braceSubstitution( $piece, $frame ) {
2968 
2969  // Flags
2970 
2971  // $text has been filled
2972  $found = false;
2973  // wiki markup in $text should be escaped
2974  $nowiki = false;
2975  // $text is HTML, armour it against wikitext transformation
2976  $isHTML = false;
2977  // Force interwiki transclusion to be done in raw mode not rendered
2978  $forceRawInterwiki = false;
2979  // $text is a DOM node needing expansion in a child frame
2980  $isChildObj = false;
2981  // $text is a DOM node needing expansion in the current frame
2982  $isLocalObj = false;
2983 
2984  # Title object, where $text came from
2985  $title = false;
2986 
2987  # $part1 is the bit before the first |, and must contain only title characters.
2988  # Various prefixes will be stripped from it later.
2989  $titleWithSpaces = $frame->expand( $piece['title'] );
2990  $part1 = trim( $titleWithSpaces );
2991  $titleText = false;
2992 
2993  # Original title text preserved for various purposes
2994  $originalTitle = $part1;
2995 
2996  # $args is a list of argument nodes, starting from index 0, not including $part1
2997  # @todo FIXME: If piece['parts'] is null then the call to getLength()
2998  # below won't work b/c this $args isn't an object
2999  $args = ( null == $piece['parts'] ) ? [] : $piece['parts'];
3000 
3001  $profileSection = null; // profile templates
3002 
3003  # SUBST
3004  if ( !$found ) {
3005  $substMatch = $this->mSubstWords->matchStartAndRemove( $part1 );
3006 
3007  # Possibilities for substMatch: "subst", "safesubst" or FALSE
3008  # Decide whether to expand template or keep wikitext as-is.
3009  if ( $this->ot['wiki'] ) {
3010  if ( $substMatch === false ) {
3011  $literal = true; # literal when in PST with no prefix
3012  } else {
3013  $literal = false; # expand when in PST with subst: or safesubst:
3014  }
3015  } else {
3016  if ( $substMatch == 'subst' ) {
3017  $literal = true; # literal when not in PST with plain subst:
3018  } else {
3019  $literal = false; # expand when not in PST with safesubst: or no prefix
3020  }
3021  }
3022  if ( $literal ) {
3023  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3024  $isLocalObj = true;
3025  $found = true;
3026  }
3027  }
3028 
3029  # Variables
3030  if ( !$found && $args->getLength() == 0 ) {
3031  $id = $this->mVariables->matchStartToEnd( $part1 );
3032  if ( $id !== false ) {
3033  $text = $this->getVariableValue( $id, $frame );
3034  if ( MagicWord::getCacheTTL( $id ) > -1 ) {
3035  $this->mOutput->updateCacheExpiry( MagicWord::getCacheTTL( $id ) );
3036  }
3037  $found = true;
3038  }
3039  }
3040 
3041  # MSG, MSGNW and RAW
3042  if ( !$found ) {
3043  # Check for MSGNW:
3044  $mwMsgnw = MagicWord::get( 'msgnw' );
3045  if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
3046  $nowiki = true;
3047  } else {
3048  # Remove obsolete MSG:
3049  $mwMsg = MagicWord::get( 'msg' );
3050  $mwMsg->matchStartAndRemove( $part1 );
3051  }
3052 
3053  # Check for RAW:
3054  $mwRaw = MagicWord::get( 'raw' );
3055  if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
3056  $forceRawInterwiki = true;
3057  }
3058  }
3059 
3060  # Parser functions
3061  if ( !$found ) {
3062  $colonPos = strpos( $part1, ':' );
3063  if ( $colonPos !== false ) {
3064  $func = substr( $part1, 0, $colonPos );
3065  $funcArgs = [ trim( substr( $part1, $colonPos + 1 ) ) ];
3066  $argsLength = $args->getLength();
3067  for ( $i = 0; $i < $argsLength; $i++ ) {
3068  $funcArgs[] = $args->item( $i );
3069  }
3070  try {
3071  $result = $this->callParserFunction( $frame, $func, $funcArgs );
3072  } catch ( Exception $ex ) {
3073  throw $ex;
3074  }
3075 
3076  # The interface for parser functions allows for extracting
3077  # flags into the local scope. Extract any forwarded flags
3078  # here.
3079  extract( $result );
3080  }
3081  }
3082 
3083  # Finish mangling title and then check for loops.
3084  # Set $title to a Title object and $titleText to the PDBK
3085  if ( !$found ) {
3086  $ns = NS_TEMPLATE;
3087  # Split the title into page and subpage
3088  $subpage = '';
3089  $relative = $this->maybeDoSubpageLink( $part1, $subpage );
3090  if ( $part1 !== $relative ) {
3091  $part1 = $relative;
3092  $ns = $this->mTitle->getNamespace();
3093  }
3094  $title = Title::newFromText( $part1, $ns );
3095  if ( $title ) {
3096  $titleText = $title->getPrefixedText();
3097  # Check for language variants if the template is not found
3098  if ( $this->getConverterLanguage()->hasVariants() && $title->getArticleID() == 0 ) {
3099  $this->getConverterLanguage()->findVariantLink( $part1, $title, true );
3100  }
3101  # Do recursion depth check
3102  $limit = $this->mOptions->getMaxTemplateDepth();
3103  if ( $frame->depth >= $limit ) {
3104  $found = true;
3105  $text = '<span class="error">'
3106  . wfMessage( 'parser-template-recursion-depth-warning' )
3107  ->numParams( $limit )->inContentLanguage()->text()
3108  . '</span>';
3109  }
3110  }
3111  }
3112 
3113  # Load from database
3114  if ( !$found && $title ) {
3115  $profileSection = $this->mProfiler->scopedProfileIn( $title->getPrefixedDBkey() );
3116  if ( !$title->isExternal() ) {
3117  if ( $title->isSpecialPage()
3118  && $this->mOptions->getAllowSpecialInclusion()
3119  && $this->ot['html']
3120  ) {
3121  $specialPage = SpecialPageFactory::getPage( $title->getDBkey() );
3122  // Pass the template arguments as URL parameters.
3123  // "uselang" will have no effect since the Language object
3124  // is forced to the one defined in ParserOptions.
3125  $pageArgs = [];
3126  $argsLength = $args->getLength();
3127  for ( $i = 0; $i < $argsLength; $i++ ) {
3128  $bits = $args->item( $i )->splitArg();
3129  if ( strval( $bits['index'] ) === '' ) {
3130  $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
3131  $value = trim( $frame->expand( $bits['value'] ) );
3132  $pageArgs[$name] = $value;
3133  }
3134  }
3135 
3136  // Create a new context to execute the special page
3137  $context = new RequestContext;
3138  $context->setTitle( $title );
3139  $context->setRequest( new FauxRequest( $pageArgs ) );
3140  if ( $specialPage && $specialPage->maxIncludeCacheTime() === 0 ) {
3141  $context->setUser( $this->getUser() );
3142  } else {
3143  // If this page is cached, then we better not be per user.
3144  $context->setUser( User::newFromName( '127.0.0.1', false ) );
3145  }
3146  $context->setLanguage( $this->mOptions->getUserLangObj() );
3147  $ret = SpecialPageFactory::capturePath( $title, $context, $this->getLinkRenderer() );
3148  if ( $ret ) {
3149  $text = $context->getOutput()->getHTML();
3150  $this->mOutput->addOutputPageMetadata( $context->getOutput() );
3151  $found = true;
3152  $isHTML = true;
3153  if ( $specialPage && $specialPage->maxIncludeCacheTime() !== false ) {
3154  $this->mOutput->updateCacheExpiry( $specialPage->maxIncludeCacheTime() );
3155  }
3156  }
3157  } elseif ( MWNamespace::isNonincludable( $title->getNamespace() ) ) {
3158  $found = false; # access denied
3159  wfDebug( __METHOD__ . ": template inclusion denied for " .
3160  $title->getPrefixedDBkey() . "\n" );
3161  } else {
3162  list( $text, $title ) = $this->getTemplateDom( $title );
3163  if ( $text !== false ) {
3164  $found = true;
3165  $isChildObj = true;
3166  }
3167  }
3168 
3169  # If the title is valid but undisplayable, make a link to it
3170  if ( !$found && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3171  $text = "[[:$titleText]]";
3172  $found = true;
3173  }
3174  } elseif ( $title->isTrans() ) {
3175  # Interwiki transclusion
3176  if ( $this->ot['html'] && !$forceRawInterwiki ) {
3177  $text = $this->interwikiTransclude( $title, 'render' );
3178  $isHTML = true;
3179  } else {
3180  $text = $this->interwikiTransclude( $title, 'raw' );
3181  # Preprocess it like a template
3182  $text = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3183  $isChildObj = true;
3184  }
3185  $found = true;
3186  }
3187 
3188  # Do infinite loop check
3189  # This has to be done after redirect resolution to avoid infinite loops via redirects
3190  if ( !$frame->loopCheck( $title ) ) {
3191  $found = true;
3192  $text = '<span class="error">'
3193  . wfMessage( 'parser-template-loop-warning', $titleText )->inContentLanguage()->text()
3194  . '</span>';
3195  wfDebug( __METHOD__ . ": template loop broken at '$titleText'\n" );
3196  }
3197  }
3198 
3199  # If we haven't found text to substitute by now, we're done
3200  # Recover the source wikitext and return it
3201  if ( !$found ) {
3202  $text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
3203  if ( $profileSection ) {
3204  $this->mProfiler->scopedProfileOut( $profileSection );
3205  }
3206  return [ 'object' => $text ];
3207  }
3208 
3209  # Expand DOM-style return values in a child frame
3210  if ( $isChildObj ) {
3211  # Clean up argument array
3212  $newFrame = $frame->newChild( $args, $title );
3213 
3214  if ( $nowiki ) {
3215  $text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
3216  } elseif ( $titleText !== false && $newFrame->isEmpty() ) {
3217  # Expansion is eligible for the empty-frame cache
3218  $text = $newFrame->cachedExpand( $titleText, $text );
3219  } else {
3220  # Uncached expansion
3221  $text = $newFrame->expand( $text );
3222  }
3223  }
3224  if ( $isLocalObj && $nowiki ) {
3225  $text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
3226  $isLocalObj = false;
3227  }
3228 
3229  if ( $profileSection ) {
3230  $this->mProfiler->scopedProfileOut( $profileSection );
3231  }
3232 
3233  # Replace raw HTML by a placeholder
3234  if ( $isHTML ) {
3235  $text = $this->insertStripItem( $text );
3236  } elseif ( $nowiki && ( $this->ot['html'] || $this->ot['pre'] ) ) {
3237  # Escape nowiki-style return values
3238  $text = wfEscapeWikiText( $text );
3239  } elseif ( is_string( $text )
3240  && !$piece['lineStart']
3241  && preg_match( '/^(?:{\\||:|;|#|\*)/', $text )
3242  ) {
3243  # Bug 529: if the template begins with a table or block-level
3244  # element, it should be treated as beginning a new line.
3245  # This behavior is somewhat controversial.
3246  $text = "\n" . $text;
3247  }
3248 
3249  if ( is_string( $text ) && !$this->incrementIncludeSize( 'post-expand', strlen( $text ) ) ) {
3250  # Error, oversize inclusion
3251  if ( $titleText !== false ) {
3252  # Make a working, properly escaped link if possible (bug 23588)
3253  $text = "[[:$titleText]]";
3254  } else {
3255  # This will probably not be a working link, but at least it may
3256  # provide some hint of where the problem is
3257  preg_replace( '/^:/', '', $originalTitle );
3258  $text = "[[:$originalTitle]]";
3259  }
3260  $text .= $this->insertStripItem( '<!-- WARNING: template omitted, '
3261  . 'post-expand include size too large -->' );
3262  $this->limitationWarn( 'post-expand-template-inclusion' );
3263  }
3264 
3265  if ( $isLocalObj ) {
3266  $ret = [ 'object' => $text ];
3267  } else {
3268  $ret = [ 'text' => $text ];
3269  }
3270 
3271  return $ret;
3272  }
3273 
3293  public function callParserFunction( $frame, $function, array $args = [] ) {
3295 
3296  # Case sensitive functions
3297  if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
3298  $function = $this->mFunctionSynonyms[1][$function];
3299  } else {
3300  # Case insensitive functions
3301  $function = $wgContLang->lc( $function );
3302  if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
3303  $function = $this->mFunctionSynonyms[0][$function];
3304  } else {
3305  return [ 'found' => false ];
3306  }
3307  }
3308 
3309  list( $callback, $flags ) = $this->mFunctionHooks[$function];
3310 
3311  # Workaround for PHP bug 35229 and similar
3312  if ( !is_callable( $callback ) ) {
3313  throw new MWException( "Tag hook for $function is not callable\n" );
3314  }
3315 
3316  $allArgs = [ &$this ];
3317  if ( $flags & self::SFH_OBJECT_ARGS ) {
3318  # Convert arguments to PPNodes and collect for appending to $allArgs
3319  $funcArgs = [];
3320  foreach ( $args as $k => $v ) {
3321  if ( $v instanceof PPNode || $k === 0 ) {
3322  $funcArgs[] = $v;
3323  } else {
3324  $funcArgs[] = $this->mPreprocessor->newPartNodeArray( [ $k => $v ] )->item( 0 );
3325  }
3326  }
3327 
3328  # Add a frame parameter, and pass the arguments as an array
3329  $allArgs[] = $frame;
3330  $allArgs[] = $funcArgs;
3331  } else {
3332  # Convert arguments to plain text and append to $allArgs
3333  foreach ( $args as $k => $v ) {
3334  if ( $v instanceof PPNode ) {
3335  $allArgs[] = trim( $frame->expand( $v ) );
3336  } elseif ( is_int( $k ) && $k >= 0 ) {
3337  $allArgs[] = trim( $v );
3338  } else {
3339  $allArgs[] = trim( "$k=$v" );
3340  }
3341  }
3342  }
3343 
3344  $result = call_user_func_array( $callback, $allArgs );
3345 
3346  # The interface for function hooks allows them to return a wikitext
3347  # string or an array containing the string and any flags. This mungs
3348  # things around to match what this method should return.
3349  if ( !is_array( $result ) ) {
3350  $result =[
3351  'found' => true,
3352  'text' => $result,
3353  ];
3354  } else {
3355  if ( isset( $result[0] ) && !isset( $result['text'] ) ) {
3356  $result['text'] = $result[0];
3357  }
3358  unset( $result[0] );
3359  $result += [
3360  'found' => true,
3361  ];
3362  }
3363 
3364  $noparse = true;
3365  $preprocessFlags = 0;
3366  if ( isset( $result['noparse'] ) ) {
3367  $noparse = $result['noparse'];
3368  }
3369  if ( isset( $result['preprocessFlags'] ) ) {
3370  $preprocessFlags = $result['preprocessFlags'];
3371  }
3372 
3373  if ( !$noparse ) {
3374  $result['text'] = $this->preprocessToDom( $result['text'], $preprocessFlags );
3375  $result['isChildObj'] = true;
3376  }
3377 
3378  return $result;
3379  }
3380 
3389  public function getTemplateDom( $title ) {
3390  $cacheTitle = $title;
3391  $titleText = $title->getPrefixedDBkey();
3392 
3393  if ( isset( $this->mTplRedirCache[$titleText] ) ) {
3394  list( $ns, $dbk ) = $this->mTplRedirCache[$titleText];
3395  $title = Title::makeTitle( $ns, $dbk );
3396  $titleText = $title->getPrefixedDBkey();
3397  }
3398  if ( isset( $this->mTplDomCache[$titleText] ) ) {
3399  return [ $this->mTplDomCache[$titleText], $title ];
3400  }
3401 
3402  # Cache miss, go to the database
3403  list( $text, $title ) = $this->fetchTemplateAndTitle( $title );
3404 
3405  if ( $text === false ) {
3406  $this->mTplDomCache[$titleText] = false;
3407  return [ false, $title ];
3408  }
3409 
3410  $dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
3411  $this->mTplDomCache[$titleText] = $dom;
3412 
3413  if ( !$title->equals( $cacheTitle ) ) {
3414  $this->mTplRedirCache[$cacheTitle->getPrefixedDBkey()] =
3415  [ $title->getNamespace(), $cdb = $title->getDBkey() ];
3416  }
3417 
3418  return [ $dom, $title ];
3419  }
3420 
3433  $cacheKey = $title->getPrefixedDBkey();
3434  if ( !$this->currentRevisionCache ) {
3435  $this->currentRevisionCache = new MapCacheLRU( 100 );
3436  }
3437  if ( !$this->currentRevisionCache->has( $cacheKey ) ) {
3438  $this->currentRevisionCache->set( $cacheKey,
3439  // Defaults to Parser::statelessFetchRevision()
3440  call_user_func( $this->mOptions->getCurrentRevisionCallback(), $title, $this )
3441  );
3442  }
3443  return $this->currentRevisionCache->get( $cacheKey );
3444  }
3445 
3455  public static function statelessFetchRevision( $title, $parser = false ) {
3456  return Revision::newFromTitle( $title );
3457  }
3458 
3464  public function fetchTemplateAndTitle( $title ) {
3465  // Defaults to Parser::statelessFetchTemplate()
3466  $templateCb = $this->mOptions->getTemplateCallback();
3467  $stuff = call_user_func( $templateCb, $title, $this );
3468  // We use U+007F DELETE to distinguish strip markers from regular text.
3469  $text = $stuff['text'];
3470  if ( is_string( $stuff['text'] ) ) {
3471  $text = strtr( $text, "\x7f", "?" );
3472  }
3473  $finalTitle = isset( $stuff['finalTitle'] ) ? $stuff['finalTitle'] : $title;
3474  if ( isset( $stuff['deps'] ) ) {
3475  foreach ( $stuff['deps'] as $dep ) {
3476  $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
3477  if ( $dep['title']->equals( $this->getTitle() ) ) {
3478  // If we transclude ourselves, the final result
3479  // will change based on the new version of the page
3480  $this->mOutput->setFlag( 'vary-revision' );
3481  }
3482  }
3483  }
3484  return [ $text, $finalTitle ];
3485  }
3486 
3492  public function fetchTemplate( $title ) {
3493  return $this->fetchTemplateAndTitle( $title )[0];
3494  }
3495 
3505  public static function statelessFetchTemplate( $title, $parser = false ) {
3506  $text = $skip = false;
3507  $finalTitle = $title;
3508  $deps = [];
3509 
3510  # Loop to fetch the article, with up to 1 redirect
3511  // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed
3512  for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
3513  // @codingStandardsIgnoreEnd
3514  # Give extensions a chance to select the revision instead
3515  $id = false; # Assume current
3516  Hooks::run( 'BeforeParserFetchTemplateAndtitle',
3517  [ $parser, $title, &$skip, &$id ] );
3518 
3519  if ( $skip ) {
3520  $text = false;
3521  $deps[] = [
3522  'title' => $title,
3523  'page_id' => $title->getArticleID(),
3524  'rev_id' => null
3525  ];
3526  break;
3527  }
3528  # Get the revision
3529  if ( $id ) {
3530  $rev = Revision::newFromId( $id );
3531  } elseif ( $parser ) {
3532  $rev = $parser->fetchCurrentRevisionOfTitle( $title );
3533  } else {
3535  }
3536  $rev_id = $rev ? $rev->getId() : 0;
3537  # If there is no current revision, there is no page
3538  if ( $id === false && !$rev ) {
3539  $linkCache = LinkCache::singleton();
3540  $linkCache->addBadLinkObj( $title );
3541  }
3542 
3543  $deps[] = [
3544  'title' => $title,
3545  'page_id' => $title->getArticleID(),
3546  'rev_id' => $rev_id ];
3547  if ( $rev && !$title->equals( $rev->getTitle() ) ) {
3548  # We fetched a rev from a different title; register it too...
3549  $deps[] = [
3550  'title' => $rev->getTitle(),
3551  'page_id' => $rev->getPage(),
3552  'rev_id' => $rev_id ];
3553  }
3554 
3555  if ( $rev ) {
3556  $content = $rev->getContent();
3557  $text = $content ? $content->getWikitextForTransclusion() : null;
3558 
3559  if ( $text === false || $text === null ) {
3560  $text = false;
3561  break;
3562  }
3563  } elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
3565  $message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
3566  if ( !$message->exists() ) {
3567  $text = false;
3568  break;
3569  }
3570  $content = $message->content();
3571  $text = $message->plain();
3572  } else {
3573  break;
3574  }
3575  if ( !$content ) {
3576  break;
3577  }
3578  # Redirect?
3579  $finalTitle = $title;
3580  $title = $content->getRedirectTarget();
3581  }
3582  return [
3583  'text' => $text,
3584  'finalTitle' => $finalTitle,
3585  'deps' => $deps ];
3586  }
3587 
3595  public function fetchFile( $title, $options = [] ) {
3596  return $this->fetchFileAndTitle( $title, $options )[0];
3597  }
3598 
3606  public function fetchFileAndTitle( $title, $options = [] ) {
3607  $file = $this->fetchFileNoRegister( $title, $options );
3608 
3609  $time = $file ? $file->getTimestamp() : false;
3610  $sha1 = $file ? $file->getSha1() : false;
3611  # Register the file as a dependency...
3612  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3613  if ( $file && !$title->equals( $file->getTitle() ) ) {
3614  # Update fetched file title
3615  $title = $file->getTitle();
3616  $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
3617  }
3618  return [ $file, $title ];
3619  }
3620 
3631  protected function fetchFileNoRegister( $title, $options = [] ) {
3632  if ( isset( $options['broken'] ) ) {
3633  $file = false; // broken thumbnail forced by hook
3634  } elseif ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
3635  $file = RepoGroup::singleton()->findFileFromKey( $options['sha1'], $options );
3636  } else { // get by (name,timestamp)
3637  $file = wfFindFile( $title, $options );
3638  }
3639  return $file;
3640  }
3641 
3650  public function interwikiTransclude( $title, $action ) {
3652 
3653  if ( !$wgEnableScaryTranscluding ) {
3654  return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
3655  }
3656 
3657  $url = $title->getFullURL( [ 'action' => $action ] );
3658 
3659  if ( strlen( $url ) > 255 ) {
3660  return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
3661  }
3662  return $this->fetchScaryTemplateMaybeFromCache( $url );
3663  }
3664 
3669  public function fetchScaryTemplateMaybeFromCache( $url ) {
3671  $dbr = wfGetDB( DB_SLAVE );
3672  $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
3673  $obj = $dbr->selectRow( 'transcache', [ 'tc_time', 'tc_contents' ],
3674  [ 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ] );
3675  if ( $obj ) {
3676  return $obj->tc_contents;
3677  }
3678 
3679  $req = MWHttpRequest::factory( $url, [], __METHOD__ );
3680  $status = $req->execute(); // Status object
3681  if ( $status->isOK() ) {
3682  $text = $req->getContent();
3683  } elseif ( $req->getStatus() != 200 ) {
3684  // Though we failed to fetch the content, this status is useless.
3685  return wfMessage( 'scarytranscludefailed-httpstatus' )
3686  ->params( $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text();
3687  } else {
3688  return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
3689  }
3690 
3691  $dbw = wfGetDB( DB_MASTER );
3692  $dbw->replace( 'transcache', [ 'tc_url' ], [
3693  'tc_url' => $url,
3694  'tc_time' => $dbw->timestamp( time() ),
3695  'tc_contents' => $text
3696  ] );
3697  return $text;
3698  }
3699 
3709  public function argSubstitution( $piece, $frame ) {
3710 
3711  $error = false;
3712  $parts = $piece['parts'];
3713  $nameWithSpaces = $frame->expand( $piece['title'] );
3714  $argName = trim( $nameWithSpaces );
3715  $object = false;
3716  $text = $frame->getArgument( $argName );
3717  if ( $text === false && $parts->getLength() > 0
3718  && ( $this->ot['html']
3719  || $this->ot['pre']
3720  || ( $this->ot['wiki'] && $frame->isTemplate() )
3721  )
3722  ) {
3723  # No match in frame, use the supplied default
3724  $object = $parts->item( 0 )->getChildren();
3725  }
3726  if ( !$this->incrementIncludeSize( 'arg', strlen( $text ) ) ) {
3727  $error = '<!-- WARNING: argument omitted, expansion size too large -->';
3728  $this->limitationWarn( 'post-expand-template-argument' );
3729  }
3730 
3731  if ( $text === false && $object === false ) {
3732  # No match anywhere
3733  $object = $frame->virtualBracketedImplode( '{{{', '|', '}}}', $nameWithSpaces, $parts );
3734  }
3735  if ( $error !== false ) {
3736  $text .= $error;
3737  }
3738  if ( $object !== false ) {
3739  $ret = [ 'object' => $object ];
3740  } else {
3741  $ret = [ 'text' => $text ];
3742  }
3743 
3744  return $ret;
3745  }
3746 
3762  public function extensionSubstitution( $params, $frame ) {
3763  $name = $frame->expand( $params['name'] );
3764  $attrText = !isset( $params['attr'] ) ? null : $frame->expand( $params['attr'] );
3765  $content = !isset( $params['inner'] ) ? null : $frame->expand( $params['inner'] );
3766  $marker = self::MARKER_PREFIX . "-$name-"
3767  . sprintf( '%08X', $this->mMarkerIndex++ ) . self::MARKER_SUFFIX;
3768 
3769  $isFunctionTag = isset( $this->mFunctionTagHooks[strtolower( $name )] ) &&
3770  ( $this->ot['html'] || $this->ot['pre'] );
3771  if ( $isFunctionTag ) {
3772  $markerType = 'none';
3773  } else {
3774  $markerType = 'general';
3775  }
3776  if ( $this->ot['html'] || $isFunctionTag ) {
3777  $name = strtolower( $name );
3778  $attributes = Sanitizer::decodeTagAttributes( $attrText );
3779  if ( isset( $params['attributes'] ) ) {
3780  $attributes = $attributes + $params['attributes'];
3781  }
3782 
3783  if ( isset( $this->mTagHooks[$name] ) ) {
3784  # Workaround for PHP bug 35229 and similar
3785  if ( !is_callable( $this->mTagHooks[$name] ) ) {
3786  throw new MWException( "Tag hook for $name is not callable\n" );
3787  }
3788  $output = call_user_func_array( $this->mTagHooks[$name],
3789  [ $content, $attributes, $this, $frame ] );
3790  } elseif ( isset( $this->mFunctionTagHooks[$name] ) ) {
3791  list( $callback, ) = $this->mFunctionTagHooks[$name];
3792  if ( !is_callable( $callback ) ) {
3793  throw new MWException( "Tag hook for $name is not callable\n" );
3794  }
3795 
3796  $output = call_user_func_array( $callback, [ &$this, $frame, $content, $attributes ] );
3797  } else {
3798  $output = '<span class="error">Invalid tag extension name: ' .
3799  htmlspecialchars( $name ) . '</span>';
3800  }
3801 
3802  if ( is_array( $output ) ) {
3803  # Extract flags to local scope (to override $markerType)
3804  $flags = $output;
3805  $output = $flags[0];
3806  unset( $flags[0] );
3807  extract( $flags );
3808  }
3809  } else {
3810  if ( is_null( $attrText ) ) {
3811  $attrText = '';
3812  }
3813  if ( isset( $params['attributes'] ) ) {
3814  foreach ( $params['attributes'] as $attrName => $attrValue ) {
3815  $attrText .= ' ' . htmlspecialchars( $attrName ) . '="' .
3816  htmlspecialchars( $attrValue ) . '"';
3817  }
3818  }
3819  if ( $content === null ) {
3820  $output = "<$name$attrText/>";
3821  } else {
3822  $close = is_null( $params['close'] ) ? '' : $frame->expand( $params['close'] );
3823  $output = "<$name$attrText>$content$close";
3824  }
3825  }
3826 
3827  if ( $markerType === 'none' ) {
3828  return $output;
3829  } elseif ( $markerType === 'nowiki' ) {
3830  $this->mStripState->addNoWiki( $marker, $output );
3831  } elseif ( $markerType === 'general' ) {
3832  $this->mStripState->addGeneral( $marker, $output );
3833  } else {
3834  throw new MWException( __METHOD__ . ': invalid marker type' );
3835  }
3836  return $marker;
3837  }
3838 
3846  public function incrementIncludeSize( $type, $size ) {
3847  if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
3848  return false;
3849  } else {
3850  $this->mIncludeSizes[$type] += $size;
3851  return true;
3852  }
3853  }
3854 
3861  $this->mExpensiveFunctionCount++;
3862  return $this->mExpensiveFunctionCount <= $this->mOptions->getExpensiveParserFunctionLimit();
3863  }
3864 
3873  public function doDoubleUnderscore( $text ) {
3874 
3875  # The position of __TOC__ needs to be recorded
3876  $mw = MagicWord::get( 'toc' );
3877  if ( $mw->match( $text ) ) {
3878  $this->mShowToc = true;
3879  $this->mForceTocPosition = true;
3880 
3881  # Set a placeholder. At the end we'll fill it in with the TOC.
3882  $text = $mw->replace( '<!--MWTOC-->', $text, 1 );
3883 
3884  # Only keep the first one.
3885  $text = $mw->replace( '', $text );
3886  }
3887 
3888  # Now match and remove the rest of them
3890  $this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
3891 
3892  if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) {
3893  $this->mOutput->mNoGallery = true;
3894  }
3895  if ( isset( $this->mDoubleUnderscores['notoc'] ) && !$this->mForceTocPosition ) {
3896  $this->mShowToc = false;
3897  }
3898  if ( isset( $this->mDoubleUnderscores['hiddencat'] )
3899  && $this->mTitle->getNamespace() == NS_CATEGORY
3900  ) {
3901  $this->addTrackingCategory( 'hidden-category-category' );
3902  }
3903  # (bug 8068) Allow control over whether robots index a page.
3904  # @todo FIXME: Bug 14899: __INDEX__ always overrides __NOINDEX__ here! This
3905  # is not desirable, the last one on the page should win.
3906  if ( isset( $this->mDoubleUnderscores['noindex'] ) && $this->mTitle->canUseNoindex() ) {
3907  $this->mOutput->setIndexPolicy( 'noindex' );
3908  $this->addTrackingCategory( 'noindex-category' );
3909  }
3910  if ( isset( $this->mDoubleUnderscores['index'] ) && $this->mTitle->canUseNoindex() ) {
3911  $this->mOutput->setIndexPolicy( 'index' );
3912  $this->addTrackingCategory( 'index-category' );
3913  }
3914 
3915  # Cache all double underscores in the database
3916  foreach ( $this->mDoubleUnderscores as $key => $val ) {
3917  $this->mOutput->setProperty( $key, '' );
3918  }
3919 
3920  return $text;
3921  }
3922 
3928  public function addTrackingCategory( $msg ) {
3929  return $this->mOutput->addTrackingCategory( $msg, $this->mTitle );
3930  }
3931 
3948  public function formatHeadings( $text, $origText, $isMain = true ) {
3949  global $wgMaxTocLevel, $wgExperimentalHtmlIds;
3950 
3951  # Inhibit editsection links if requested in the page
3952  if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
3953  $maybeShowEditLink = $showEditLink = false;
3954  } else {
3955  $maybeShowEditLink = true; /* Actual presence will depend on ParserOptions option */
3956  $showEditLink = $this->mOptions->getEditSection();
3957  }
3958  if ( $showEditLink ) {
3959  $this->mOutput->setEditSectionTokens( true );
3960  }
3961 
3962  # Get all headlines for numbering them and adding funky stuff like [edit]
3963  # links - this is for later, but we need the number of headlines right now
3964  $matches = [];
3965  $numMatches = preg_match_all(
3966  '/<H(?P<level>[1-6])(?P<attrib>.*?>)\s*(?P<header>[\s\S]*?)\s*<\/H[1-6] *>/i',
3967  $text,
3968  $matches
3969  );
3970 
3971  # if there are fewer than 4 headlines in the article, do not show TOC
3972  # unless it's been explicitly enabled.
3973  $enoughToc = $this->mShowToc &&
3974  ( ( $numMatches >= 4 ) || $this->mForceTocPosition );
3975 
3976  # Allow user to stipulate that a page should have a "new section"
3977  # link added via __NEWSECTIONLINK__
3978  if ( isset( $this->mDoubleUnderscores['newsectionlink'] ) ) {
3979  $this->mOutput->setNewSection( true );
3980  }
3981 
3982  # Allow user to remove the "new section"
3983  # link via __NONEWSECTIONLINK__
3984  if ( isset( $this->mDoubleUnderscores['nonewsectionlink'] ) ) {
3985  $this->mOutput->hideNewSection( true );
3986  }
3987 
3988  # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
3989  # override above conditions and always show TOC above first header
3990  if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) {
3991  $this->mShowToc = true;
3992  $enoughToc = true;
3993  }
3994 
3995  # headline counter
3996  $headlineCount = 0;
3997  $numVisible = 0;
3998 
3999  # Ugh .. the TOC should have neat indentation levels which can be
4000  # passed to the skin functions. These are determined here
4001  $toc = '';
4002  $full = '';
4003  $head = [];
4004  $sublevelCount = [];
4005  $levelCount = [];
4006  $level = 0;
4007  $prevlevel = 0;
4008  $toclevel = 0;
4009  $prevtoclevel = 0;
4010  $markerRegex = self::MARKER_PREFIX . "-h-(\d+)-" . self::MARKER_SUFFIX;
4011  $baseTitleText = $this->mTitle->getPrefixedDBkey();
4012  $oldType = $this->mOutputType;
4013  $this->setOutputType( self::OT_WIKI );
4014  $frame = $this->getPreprocessor()->newFrame();
4015  $root = $this->preprocessToDom( $origText );
4016  $node = $root->getFirstChild();
4017  $byteOffset = 0;
4018  $tocraw = [];
4019  $refers = [];
4020 
4021  $headlines = $numMatches !== false ? $matches[3] : [];
4022 
4023  foreach ( $headlines as $headline ) {
4024  $isTemplate = false;
4025  $titleText = false;
4026  $sectionIndex = false;
4027  $numbering = '';
4028  $markerMatches = [];
4029  if ( preg_match( "/^$markerRegex/", $headline, $markerMatches ) ) {
4030  $serial = $markerMatches[1];
4031  list( $titleText, $sectionIndex ) = $this->mHeadings[$serial];
4032  $isTemplate = ( $titleText != $baseTitleText );
4033  $headline = preg_replace( "/^$markerRegex\\s*/", "", $headline );
4034  }
4035 
4036  if ( $toclevel ) {
4037  $prevlevel = $level;
4038  }
4039  $level = $matches[1][$headlineCount];
4040 
4041  if ( $level > $prevlevel ) {
4042  # Increase TOC level
4043  $toclevel++;
4044  $sublevelCount[$toclevel] = 0;
4045  if ( $toclevel < $wgMaxTocLevel ) {
4046  $prevtoclevel = $toclevel;
4047  $toc .= Linker::tocIndent();
4048  $numVisible++;
4049  }
4050  } elseif ( $level < $prevlevel && $toclevel > 1 ) {
4051  # Decrease TOC level, find level to jump to
4052 
4053  for ( $i = $toclevel; $i > 0; $i-- ) {
4054  if ( $levelCount[$i] == $level ) {
4055  # Found last matching level
4056  $toclevel = $i;
4057  break;
4058  } elseif ( $levelCount[$i] < $level ) {
4059  # Found first matching level below current level
4060  $toclevel = $i + 1;
4061  break;
4062  }
4063  }
4064  if ( $i == 0 ) {
4065  $toclevel = 1;
4066  }
4067  if ( $toclevel < $wgMaxTocLevel ) {
4068  if ( $prevtoclevel < $wgMaxTocLevel ) {
4069  # Unindent only if the previous toc level was shown :p
4070  $toc .= Linker::tocUnindent( $prevtoclevel - $toclevel );
4071  $prevtoclevel = $toclevel;
4072  } else {
4073  $toc .= Linker::tocLineEnd();
4074  }
4075  }
4076  } else {
4077  # No change in level, end TOC line
4078  if ( $toclevel < $wgMaxTocLevel ) {
4079  $toc .= Linker::tocLineEnd();
4080  }
4081  }
4082 
4083  $levelCount[$toclevel] = $level;
4084 
4085  # count number of headlines for each level
4086  $sublevelCount[$toclevel]++;
4087  $dot = 0;
4088  for ( $i = 1; $i <= $toclevel; $i++ ) {
4089  if ( !empty( $sublevelCount[$i] ) ) {
4090  if ( $dot ) {
4091  $numbering .= '.';
4092  }
4093  $numbering .= $this->getTargetLanguage()->formatNum( $sublevelCount[$i] );
4094  $dot = 1;
4095  }
4096  }
4097 
4098  # The safe header is a version of the header text safe to use for links
4099 
4100  # Remove link placeholders by the link text.
4101  # <!--LINK number-->
4102  # turns into
4103  # link text with suffix
4104  # Do this before unstrip since link text can contain strip markers
4105  $safeHeadline = $this->replaceLinkHoldersText( $headline );
4106 
4107  # Avoid insertion of weird stuff like <math> by expanding the relevant sections
4108  $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
4109 
4110  # Strip out HTML (first regex removes any tag not allowed)
4111  # Allowed tags are:
4112  # * <sup> and <sub> (bug 8393)
4113  # * <i> (bug 26375)
4114  # * <b> (r105284)
4115  # * <bdi> (bug 72884)
4116  # * <span dir="rtl"> and <span dir="ltr"> (bug 35167)
4117  # We strip any parameter from accepted tags (second regex), except dir="rtl|ltr" from <span>,
4118  # to allow setting directionality in toc items.
4119  $tocline = preg_replace(
4120  [
4121  '#<(?!/?(span|sup|sub|bdi|i|b)(?: [^>]*)?>).*?>#',
4122  '#<(/?(?:span(?: dir="(?:rtl|ltr)")?|sup|sub|bdi|i|b))(?: .*?)?>#'
4123  ],
4124  [ '', '<$1>' ],
4125  $safeHeadline
4126  );
4127 
4128  # Strip '<span></span>', which is the result from the above if
4129  # <span id="foo"></span> is used to produce an additional anchor
4130  # for a section.
4131  $tocline = str_replace( '<span></span>', '', $tocline );
4132 
4133  $tocline = trim( $tocline );
4134 
4135  # For the anchor, strip out HTML-y stuff period
4136  $safeHeadline = preg_replace( '/<.*?>/', '', $safeHeadline );
4137  $safeHeadline = Sanitizer::normalizeSectionNameWhitespace( $safeHeadline );
4138 
4139  # Save headline for section edit hint before it's escaped
4140  $headlineHint = $safeHeadline;
4141 
4142  if ( $wgExperimentalHtmlIds ) {
4143  # For reverse compatibility, provide an id that's
4144  # HTML4-compatible, like we used to.
4145  # It may be worth noting, academically, that it's possible for
4146  # the legacy anchor to conflict with a non-legacy headline
4147  # anchor on the page. In this case likely the "correct" thing
4148  # would be to either drop the legacy anchors or make sure
4149  # they're numbered first. However, this would require people
4150  # to type in section names like "abc_.D7.93.D7.90.D7.A4"
4151  # manually, so let's not bother worrying about it.
4152  $legacyHeadline = Sanitizer::escapeId( $safeHeadline,
4153  [ 'noninitial', 'legacy' ] );
4154  $safeHeadline = Sanitizer::escapeId( $safeHeadline );
4155 
4156  if ( $legacyHeadline == $safeHeadline ) {
4157  # No reason to have both (in fact, we can't)
4158  $legacyHeadline = false;
4159  }
4160  } else {
4161  $legacyHeadline = false;
4162  $safeHeadline = Sanitizer::escapeId( $safeHeadline,
4163  'noninitial' );
4164  }
4165 
4166  # HTML names must be case-insensitively unique (bug 10721).
4167  # This does not apply to Unicode characters per
4168  # http://www.w3.org/TR/html5/infrastructure.html#case-sensitivity-and-string-comparison
4169  # @todo FIXME: We may be changing them depending on the current locale.
4170  $arrayKey = strtolower( $safeHeadline );
4171  if ( $legacyHeadline === false ) {
4172  $legacyArrayKey = false;
4173  } else {
4174  $legacyArrayKey = strtolower( $legacyHeadline );
4175  }
4176 
4177  # Create the anchor for linking from the TOC to the section
4178  $anchor = $safeHeadline;
4179  $legacyAnchor = $legacyHeadline;
4180  if ( isset( $refers[$arrayKey] ) ) {
4181  // @codingStandardsIgnoreStart
4182  for ( $i = 2; isset( $refers["${arrayKey}_$i"] ); ++$i );
4183  // @codingStandardsIgnoreEnd
4184  $anchor .= "_$i";
4185  $refers["${arrayKey}_$i"] = true;
4186  } else {
4187  $refers[$arrayKey] = true;
4188  }
4189  if ( $legacyHeadline !== false && isset( $refers[$legacyArrayKey] ) ) {
4190  // @codingStandardsIgnoreStart
4191  for ( $i = 2; isset( $refers["${legacyArrayKey}_$i"] ); ++$i );
4192  // @codingStandardsIgnoreEnd
4193  $legacyAnchor .= "_$i";
4194  $refers["${legacyArrayKey}_$i"] = true;
4195  } else {
4196  $refers[$legacyArrayKey] = true;
4197  }
4198 
4199  # Don't number the heading if it is the only one (looks silly)
4200  if ( count( $matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) {
4201  # the two are different if the line contains a link
4202  $headline = Html::element(
4203  'span',
4204  [ 'class' => 'mw-headline-number' ],
4205  $numbering
4206  ) . ' ' . $headline;
4207  }
4208 
4209  if ( $enoughToc && ( !isset( $wgMaxTocLevel ) || $toclevel < $wgMaxTocLevel ) ) {
4210  $toc .= Linker::tocLine( $anchor, $tocline,
4211  $numbering, $toclevel, ( $isTemplate ? false : $sectionIndex ) );
4212  }
4213 
4214  # Add the section to the section tree
4215  # Find the DOM node for this header
4216  $noOffset = ( $isTemplate || $sectionIndex === false );
4217  while ( $node && !$noOffset ) {
4218  if ( $node->getName() === 'h' ) {
4219  $bits = $node->splitHeading();
4220  if ( $bits['i'] == $sectionIndex ) {
4221  break;
4222  }
4223  }
4224  $byteOffset += mb_strlen( $this->mStripState->unstripBoth(
4225  $frame->expand( $node, PPFrame::RECOVER_ORIG ) ) );
4226  $node = $node->getNextSibling();
4227  }
4228  $tocraw[] = [
4229  'toclevel' => $toclevel,
4230  'level' => $level,
4231  'line' => $tocline,
4232  'number' => $numbering,
4233  'index' => ( $isTemplate ? 'T-' : '' ) . $sectionIndex,
4234  'fromtitle' => $titleText,
4235  'byteoffset' => ( $noOffset ? null : $byteOffset ),
4236  'anchor' => $anchor,
4237  ];
4238 
4239  # give headline the correct <h#> tag
4240  if ( $maybeShowEditLink && $sectionIndex !== false ) {
4241  // Output edit section links as markers with styles that can be customized by skins
4242  if ( $isTemplate ) {
4243  # Put a T flag in the section identifier, to indicate to extractSections()
4244  # that sections inside <includeonly> should be counted.
4245  $editsectionPage = $titleText;
4246  $editsectionSection = "T-$sectionIndex";
4247  $editsectionContent = null;
4248  } else {
4249  $editsectionPage = $this->mTitle->getPrefixedText();
4250  $editsectionSection = $sectionIndex;
4251  $editsectionContent = $headlineHint;
4252  }
4253  // We use a bit of pesudo-xml for editsection markers. The
4254  // language converter is run later on. Using a UNIQ style marker
4255  // leads to the converter screwing up the tokens when it
4256  // converts stuff. And trying to insert strip tags fails too. At
4257  // this point all real inputted tags have already been escaped,
4258  // so we don't have to worry about a user trying to input one of
4259  // these markers directly. We use a page and section attribute
4260  // to stop the language converter from converting these
4261  // important bits of data, but put the headline hint inside a
4262  // content block because the language converter is supposed to
4263  // be able to convert that piece of data.
4264  // Gets replaced with html in ParserOutput::getText
4265  $editlink = '<mw:editsection page="' . htmlspecialchars( $editsectionPage );
4266  $editlink .= '" section="' . htmlspecialchars( $editsectionSection ) . '"';
4267  if ( $editsectionContent !== null ) {
4268  $editlink .= '>' . $editsectionContent . '</mw:editsection>';
4269  } else {
4270  $editlink .= '/>';
4271  }
4272  } else {
4273  $editlink = '';
4274  }
4275  $head[$headlineCount] = Linker::makeHeadline( $level,
4276  $matches['attrib'][$headlineCount], $anchor, $headline,
4277  $editlink, $legacyAnchor );
4278 
4279  $headlineCount++;
4280  }
4281 
4282  $this->setOutputType( $oldType );
4283 
4284  # Never ever show TOC if no headers
4285  if ( $numVisible < 1 ) {
4286  $enoughToc = false;
4287  }
4288 
4289  if ( $enoughToc ) {
4290  if ( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
4291  $toc .= Linker::tocUnindent( $prevtoclevel - 1 );
4292  }
4293  $toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
4294  $this->mOutput->setTOCHTML( $toc );
4295  $toc = self::TOC_START . $toc . self::TOC_END;
4296  $this->mOutput->addModules( 'mediawiki.toc' );
4297  }
4298 
4299  if ( $isMain ) {
4300  $this->mOutput->setSections( $tocraw );
4301  }
4302 
4303  # split up and insert constructed headlines
4304  $blocks = preg_split( '/<H[1-6].*?>[\s\S]*?<\/H[1-6]>/i', $text );
4305  $i = 0;
4306 
4307  // build an array of document sections
4308  $sections = [];
4309  foreach ( $blocks as $block ) {
4310  // $head is zero-based, sections aren't.
4311  if ( empty( $head[$i - 1] ) ) {
4312  $sections[$i] = $block;
4313  } else {
4314  $sections[$i] = $head[$i - 1] . $block;
4315  }
4316 
4327  Hooks::run( 'ParserSectionCreate', [ $this, $i, &$sections[$i], $showEditLink ] );
4328 
4329  $i++;
4330  }
4331 
4332  if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
4333  // append the TOC at the beginning
4334  // Top anchor now in skin
4335  $sections[0] = $sections[0] . $toc . "\n";
4336  }
4337 
4338  $full .= implode( '', $sections );
4339 
4340  if ( $this->mForceTocPosition ) {
4341  return str_replace( '<!--MWTOC-->', $toc, $full );
4342  } else {
4343  return $full;
4344  }
4345  }
4346 
4358  public function preSaveTransform( $text, Title $title, User $user,
4359  ParserOptions $options, $clearState = true
4360  ) {
4361  if ( $clearState ) {
4362  $magicScopeVariable = $this->lock();
4363  }
4364  $this->startParse( $title, $options, self::OT_WIKI, $clearState );
4365  $this->setUser( $user );
4366 
4367  $pairs = [
4368  "\r\n" => "\n",
4369  "\r" => "\n",
4370  ];
4371  $text = str_replace( array_keys( $pairs ), array_values( $pairs ), $text );
4372  if ( $options->getPreSaveTransform() ) {
4373  $text = $this->pstPass2( $text, $user );
4374  }
4375  $text = $this->mStripState->unstripBoth( $text );
4376 
4377  $this->setUser( null ); # Reset
4378 
4379  return $text;
4380  }
4381 
4390  private function pstPass2( $text, $user ) {
4392 
4393  # Note: This is the timestamp saved as hardcoded wikitext to
4394  # the database, we use $wgContLang here in order to give
4395  # everyone the same signature and use the default one rather
4396  # than the one selected in each user's preferences.
4397  # (see also bug 12815)
4398  $ts = $this->mOptions->getTimestamp();
4400  $ts = $timestamp->format( 'YmdHis' );
4401  $tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
4402 
4403  $d = $wgContLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
4404 
4405  # Variable replacement
4406  # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
4407  $text = $this->replaceVariables( $text );
4408 
4409  # This works almost by chance, as the replaceVariables are done before the getUserSig(),
4410  # which may corrupt this parser instance via its wfMessage()->text() call-
4411 
4412  # Signatures
4413  $sigText = $this->getUserSig( $user );
4414  $text = strtr( $text, [
4415  '~~~~~' => $d,
4416  '~~~~' => "$sigText $d",
4417  '~~~' => $sigText
4418  ] );
4419 
4420  # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
4421  $tc = '[' . Title::legalChars() . ']';
4422  $nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
4423 
4424  // [[ns:page (context)|]]
4425  $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/";
4426  // [[ns:page(context)|]] (double-width brackets, added in r40257)
4427  $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/";
4428  // [[ns:page (context), context|]] (using either single or double-width comma)
4429  $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)((?:, |,)$tc+|)\\|]]/";
4430  // [[|page]] (reverse pipe trick: add context from page title)
4431  $p2 = "/\[\[\\|($tc+)]]/";
4432 
4433  # try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
4434  $text = preg_replace( $p1, '[[\\1\\2\\3|\\2]]', $text );
4435  $text = preg_replace( $p4, '[[\\1\\2\\3|\\2]]', $text );
4436  $text = preg_replace( $p3, '[[\\1\\2\\3\\4|\\2]]', $text );
4437 
4438  $t = $this->mTitle->getText();
4439  $m = [];
4440  if ( preg_match( "/^($nc+:|)$tc+?( \\($tc+\\))$/", $t, $m ) ) {
4441  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4442  } elseif ( preg_match( "/^($nc+:|)$tc+?(, $tc+|)$/", $t, $m ) && "$m[1]$m[2]" != '' ) {
4443  $text = preg_replace( $p2, "[[$m[1]\\1$m[2]|\\1]]", $text );
4444  } else {
4445  # if there's no context, don't bother duplicating the title
4446  $text = preg_replace( $p2, '[[\\1]]', $text );
4447  }
4448 
4449  # Trim trailing whitespace
4450  $text = rtrim( $text );
4451 
4452  return $text;
4453  }
4454 
4469  public function getUserSig( &$user, $nickname = false, $fancySig = null ) {
4471 
4472  $username = $user->getName();
4473 
4474  # If not given, retrieve from the user object.
4475  if ( $nickname === false ) {
4476  $nickname = $user->getOption( 'nickname' );
4477  }
4478 
4479  if ( is_null( $fancySig ) ) {
4480  $fancySig = $user->getBoolOption( 'fancysig' );
4481  }
4482 
4483  $nickname = $nickname == null ? $username : $nickname;
4484 
4485  if ( mb_strlen( $nickname ) > $wgMaxSigChars ) {
4486  $nickname = $username;
4487  wfDebug( __METHOD__ . ": $username has overlong signature.\n" );
4488  } elseif ( $fancySig !== false ) {
4489  # Sig. might contain markup; validate this
4490  if ( $this->validateSig( $nickname ) !== false ) {
4491  # Validated; clean up (if needed) and return it
4492  return $this->cleanSig( $nickname, true );
4493  } else {
4494  # Failed to validate; fall back to the default
4495  $nickname = $username;
4496  wfDebug( __METHOD__ . ": $username has bad XML tags in signature.\n" );
4497  }
4498  }
4499 
4500  # Make sure nickname doesnt get a sig in a sig
4501  $nickname = self::cleanSigInSig( $nickname );
4502 
4503  # If we're still here, make it a link to the user page
4504  $userText = wfEscapeWikiText( $username );
4505  $nickText = wfEscapeWikiText( $nickname );
4506  $msgName = $user->isAnon() ? 'signature-anon' : 'signature';
4507 
4508  return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()
4509  ->title( $this->getTitle() )->text();
4510  }
4511 
4518  public function validateSig( $text ) {
4519  return Xml::isWellFormedXmlFragment( $text ) ? $text : false;
4520  }
4521 
4532  public function cleanSig( $text, $parsing = false ) {
4533  if ( !$parsing ) {
4534  global $wgTitle;
4535  $magicScopeVariable = $this->lock();
4536  $this->startParse( $wgTitle, new ParserOptions, self::OT_PREPROCESS, true );
4537  }
4538 
4539  # Option to disable this feature
4540  if ( !$this->mOptions->getCleanSignatures() ) {
4541  return $text;
4542  }
4543 
4544  # @todo FIXME: Regex doesn't respect extension tags or nowiki
4545  # => Move this logic to braceSubstitution()
4546  $substWord = MagicWord::get( 'subst' );
4547  $substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
4548  $substText = '{{' . $substWord->getSynonym( 0 );
4549 
4550  $text = preg_replace( $substRegex, $substText, $text );
4551  $text = self::cleanSigInSig( $text );
4552  $dom = $this->preprocessToDom( $text );
4553  $frame = $this->getPreprocessor()->newFrame();
4554  $text = $frame->expand( $dom );
4555 
4556  if ( !$parsing ) {
4557  $text = $this->mStripState->unstripBoth( $text );
4558  }
4559 
4560  return $text;
4561  }
4562 
4569  public static function cleanSigInSig( $text ) {
4570  $text = preg_replace( '/~{3,5}/', '', $text );
4571  return $text;
4572  }
4573 
4584  $outputType, $clearState = true
4585  ) {
4586  $this->startParse( $title, $options, $outputType, $clearState );
4587  }
4588 
4595  private function startParse( Title $title = null, ParserOptions $options,
4596  $outputType, $clearState = true
4597  ) {
4598  $this->setTitle( $title );
4599  $this->mOptions = $options;
4600  $this->setOutputType( $outputType );
4601  if ( $clearState ) {
4602  $this->clearState();
4603  }
4604  }
4605 
4614  public function transformMsg( $text, $options, $title = null ) {
4615  static $executing = false;
4616 
4617  # Guard against infinite recursion
4618  if ( $executing ) {
4619  return $text;
4620  }
4621  $executing = true;
4622 
4623  if ( !$title ) {
4624  global $wgTitle;
4625  $title = $wgTitle;
4626  }
4627 
4628  $text = $this->preprocess( $text, $title, $options );
4629 
4630  $executing = false;
4631  return $text;
4632  }
4633 
4658  public function setHook( $tag, $callback ) {
4659  $tag = strtolower( $tag );
4660  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4661  throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
4662  }
4663  $oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null;
4664  $this->mTagHooks[$tag] = $callback;
4665  if ( !in_array( $tag, $this->mStripList ) ) {
4666  $this->mStripList[] = $tag;
4667  }
4668 
4669  return $oldVal;
4670  }
4671 
4689  public function setTransparentTagHook( $tag, $callback ) {
4690  $tag = strtolower( $tag );
4691  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4692  throw new MWException( "Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
4693  }
4694  $oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] : null;
4695  $this->mTransparentTagHooks[$tag] = $callback;
4696 
4697  return $oldVal;
4698  }
4699 
4703  public function clearTagHooks() {
4704  $this->mTagHooks = [];
4705  $this->mFunctionTagHooks = [];
4706  $this->mStripList = $this->mDefaultStripList;
4707  }
4708 
4752  public function setFunctionHook( $id, $callback, $flags = 0 ) {
4754 
4755  $oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] : null;
4756  $this->mFunctionHooks[$id] = [ $callback, $flags ];
4757 
4758  # Add to function cache
4759  $mw = MagicWord::get( $id );
4760  if ( !$mw ) {
4761  throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
4762  }
4763 
4764  $synonyms = $mw->getSynonyms();
4765  $sensitive = intval( $mw->isCaseSensitive() );
4766 
4767  foreach ( $synonyms as $syn ) {
4768  # Case
4769  if ( !$sensitive ) {
4770  $syn = $wgContLang->lc( $syn );
4771  }
4772  # Add leading hash
4773  if ( !( $flags & self::SFH_NO_HASH ) ) {
4774  $syn = '#' . $syn;
4775  }
4776  # Remove trailing colon
4777  if ( substr( $syn, -1, 1 ) === ':' ) {
4778  $syn = substr( $syn, 0, -1 );
4779  }
4780  $this->mFunctionSynonyms[$sensitive][$syn] = $id;
4781  }
4782  return $oldVal;
4783  }
4784 
4790  public function getFunctionHooks() {
4791  return array_keys( $this->mFunctionHooks );
4792  }
4793 
4804  public function setFunctionTagHook( $tag, $callback, $flags ) {
4805  $tag = strtolower( $tag );
4806  if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) {
4807  throw new MWException( "Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
4808  }
4809  $old = isset( $this->mFunctionTagHooks[$tag] ) ?
4810  $this->mFunctionTagHooks[$tag] : null;
4811  $this->mFunctionTagHooks[$tag] = [ $callback, $flags ];
4812 
4813  if ( !in_array( $tag, $this->mStripList ) ) {
4814  $this->mStripList[] = $tag;
4815  }
4816 
4817  return $old;
4818  }
4819 
4827  public function replaceLinkHolders( &$text, $options = 0 ) {
4828  $this->mLinkHolders->replace( $text );
4829  }
4830 
4838  public function replaceLinkHoldersText( $text ) {
4839  return $this->mLinkHolders->replaceText( $text );
4840  }
4841 
4855  public function renderImageGallery( $text, $params ) {
4856 
4857  $mode = false;
4858  if ( isset( $params['mode'] ) ) {
4859  $mode = $params['mode'];
4860  }
4861 
4862  try {
4863  $ig = ImageGalleryBase::factory( $mode );
4864  } catch ( Exception $e ) {
4865  // If invalid type set, fallback to default.
4866  $ig = ImageGalleryBase::factory( false );
4867  }
4868 
4869  $ig->setContextTitle( $this->mTitle );
4870  $ig->setShowBytes( false );
4871  $ig->setShowFilename( false );
4872  $ig->setParser( $this );
4873  $ig->setHideBadImages();
4874  $ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'table' ) );
4875 
4876  if ( isset( $params['showfilename'] ) ) {
4877  $ig->setShowFilename( true );
4878  } else {
4879  $ig->setShowFilename( false );
4880  }
4881  if ( isset( $params['caption'] ) ) {
4882  $caption = $params['caption'];
4883  $caption = htmlspecialchars( $caption );
4884  $caption = $this->replaceInternalLinks( $caption );
4885  $ig->setCaptionHtml( $caption );
4886  }
4887  if ( isset( $params['perrow'] ) ) {
4888  $ig->setPerRow( $params['perrow'] );
4889  }
4890  if ( isset( $params['widths'] ) ) {
4891  $ig->setWidths( $params['widths'] );
4892  }
4893  if ( isset( $params['heights'] ) ) {
4894  $ig->setHeights( $params['heights'] );
4895  }
4896  $ig->setAdditionalOptions( $params );
4897 
4898  Hooks::run( 'BeforeParserrenderImageGallery', [ &$this, &$ig ] );
4899 
4900  $lines = StringUtils::explode( "\n", $text );
4901  foreach ( $lines as $line ) {
4902  # match lines like these:
4903  # Image:someimage.jpg|This is some image
4904  $matches = [];
4905  preg_match( "/^([^|]+)(\\|(.*))?$/", $line, $matches );
4906  # Skip empty lines
4907  if ( count( $matches ) == 0 ) {
4908  continue;
4909  }
4910 
4911  if ( strpos( $matches[0], '%' ) !== false ) {
4912  $matches[1] = rawurldecode( $matches[1] );
4913  }
4915  if ( is_null( $title ) ) {
4916  # Bogus title. Ignore these so we don't bomb out later.
4917  continue;
4918  }
4919 
4920  # We need to get what handler the file uses, to figure out parameters.
4921  # Note, a hook can overide the file name, and chose an entirely different
4922  # file (which potentially could be of a different type and have different handler).
4923  $options = [];
4924  $descQuery = false;
4925  Hooks::run( 'BeforeParserFetchFileAndTitle',
4926  [ $this, $title, &$options, &$descQuery ] );
4927  # Don't register it now, as ImageGallery does that later.
4928  $file = $this->fetchFileNoRegister( $title, $options );
4929  $handler = $file ? $file->getHandler() : false;
4930 
4931  $paramMap = [
4932  'img_alt' => 'gallery-internal-alt',
4933  'img_link' => 'gallery-internal-link',
4934  ];
4935  if ( $handler ) {
4936  $paramMap = $paramMap + $handler->getParamMap();
4937  // We don't want people to specify per-image widths.
4938  // Additionally the width parameter would need special casing anyhow.
4939  unset( $paramMap['img_width'] );
4940  }
4941 
4942  $mwArray = new MagicWordArray( array_keys( $paramMap ) );
4943 
4944  $label = '';
4945  $alt = '';
4946  $link = '';
4947  $handlerOptions = [];
4948  if ( isset( $matches[3] ) ) {
4949  // look for an |alt= definition while trying not to break existing
4950  // captions with multiple pipes (|) in it, until a more sensible grammar
4951  // is defined for images in galleries
4952 
4953  // FIXME: Doing recursiveTagParse at this stage, and the trim before
4954  // splitting on '|' is a bit odd, and different from makeImage.
4955  $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
4956  $parameterMatches = StringUtils::explode( '|', $matches[3] );
4957 
4958  foreach ( $parameterMatches as $parameterMatch ) {
4959  list( $magicName, $match ) = $mwArray->matchVariableStartToEnd( $parameterMatch );
4960  if ( $magicName ) {
4961  $paramName = $paramMap[$magicName];
4962 
4963  switch ( $paramName ) {
4964  case 'gallery-internal-alt':
4965  $alt = $this->stripAltText( $match, false );
4966  break;
4967  case 'gallery-internal-link':
4968  $linkValue = strip_tags( $this->replaceLinkHoldersText( $match ) );
4969  $chars = self::EXT_LINK_URL_CLASS;
4970  $addr = self::EXT_LINK_ADDR;
4971  $prots = $this->mUrlProtocols;
4972  // check to see if link matches an absolute url, if not then it must be a wiki link.
4973  if ( preg_match( "/^($prots)$addr$chars*$/u", $linkValue ) ) {
4974  $link = $linkValue;
4975  } else {
4976  $localLinkTitle = Title::newFromText( $linkValue );
4977  if ( $localLinkTitle !== null ) {
4978  $link = $localLinkTitle->getLinkURL();
4979  }
4980  }
4981  break;
4982  default:
4983  // Must be a handler specific parameter.
4984  if ( $handler->validateParam( $paramName, $match ) ) {
4985  $handlerOptions[$paramName] = $match;
4986  } else {
4987  // Guess not, consider it as caption.
4988  wfDebug( "$parameterMatch failed parameter validation\n" );
4989  $label = '|' . $parameterMatch;
4990  }
4991  }
4992 
4993  } else {
4994  // Last pipe wins.
4995  $label = '|' . $parameterMatch;
4996  }
4997  }
4998  // Remove the pipe.
4999  $label = substr( $label, 1 );
5000  }
5001 
5002  $ig->add( $title, $label, $alt, $link, $handlerOptions );
5003  }
5004  $html = $ig->toHTML();
5005  Hooks::run( 'AfterParserFetchFileAndTitle', [ $this, $ig, &$html ] );
5006  return $html;
5007  }
5008 
5013  public function getImageParams( $handler ) {
5014  if ( $handler ) {
5015  $handlerClass = get_class( $handler );
5016  } else {
5017  $handlerClass = '';
5018  }
5019  if ( !isset( $this->mImageParams[$handlerClass] ) ) {
5020  # Initialise static lists
5021  static $internalParamNames = [
5022  'horizAlign' => [ 'left', 'right', 'center', 'none' ],
5023  'vertAlign' => [ 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
5024  'bottom', 'text-bottom' ],
5025  'frame' => [ 'thumbnail', 'manualthumb', 'framed', 'frameless',
5026  'upright', 'border', 'link', 'alt', 'class' ],
5027  ];
5028  static $internalParamMap;
5029  if ( !$internalParamMap ) {
5030  $internalParamMap = [];
5031  foreach ( $internalParamNames as $type => $names ) {
5032  foreach ( $names as $name ) {
5033  $magicName = str_replace( '-', '_', "img_$name" );
5034  $internalParamMap[$magicName] = [ $type, $name ];
5035  }
5036  }
5037  }
5038 
5039  # Add handler params
5040  $paramMap = $internalParamMap;
5041  if ( $handler ) {
5042  $handlerParamMap = $handler->getParamMap();
5043  foreach ( $handlerParamMap as $magic => $paramName ) {
5044  $paramMap[$magic] = [ 'handler', $paramName ];
5045  }
5046  }
5047  $this->mImageParams[$handlerClass] = $paramMap;
5048  $this->mImageParamsMagicArray[$handlerClass] = new MagicWordArray( array_keys( $paramMap ) );
5049  }
5050  return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
5051  }
5052 
5061  public function makeImage( $title, $options, $holders = false ) {
5062  # Check if the options text is of the form "options|alt text"
5063  # Options are:
5064  # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
5065  # * left no resizing, just left align. label is used for alt= only
5066  # * right same, but right aligned
5067  # * none same, but not aligned
5068  # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
5069  # * center center the image
5070  # * frame Keep original image size, no magnify-button.
5071  # * framed Same as "frame"
5072  # * frameless like 'thumb' but without a frame. Keeps user preferences for width
5073  # * upright reduce width for upright images, rounded to full __0 px
5074  # * border draw a 1px border around the image
5075  # * alt Text for HTML alt attribute (defaults to empty)
5076  # * class Set a class for img node
5077  # * link Set the target of the image link. Can be external, interwiki, or local
5078  # vertical-align values (no % or length right now):
5079  # * baseline
5080  # * sub
5081  # * super
5082  # * top
5083  # * text-top
5084  # * middle
5085  # * bottom
5086  # * text-bottom
5087 
5088  $parts = StringUtils::explode( "|", $options );
5089 
5090  # Give extensions a chance to select the file revision for us
5091  $options = [];
5092  $descQuery = false;
5093  Hooks::run( 'BeforeParserFetchFileAndTitle',
5094  [ $this, $title, &$options, &$descQuery ] );
5095  # Fetch and register the file (file title may be different via hooks)
5096  list( $file, $title ) = $this->fetchFileAndTitle( $title, $options );
5097 
5098  # Get parameter map
5099  $handler = $file ? $file->getHandler() : false;
5100 
5101  list( $paramMap, $mwArray ) = $this->getImageParams( $handler );
5102 
5103  if ( !$file ) {
5104  $this->addTrackingCategory( 'broken-file-category' );
5105  }
5106 
5107  # Process the input parameters
5108  $caption = '';
5109  $params = [ 'frame' => [], 'handler' => [],
5110  'horizAlign' => [], 'vertAlign' => [] ];
5111  $seenformat = false;
5112  foreach ( $parts as $part ) {
5113  $part = trim( $part );
5114  list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part );
5115  $validated = false;
5116  if ( isset( $paramMap[$magicName] ) ) {
5117  list( $type, $paramName ) = $paramMap[$magicName];
5118 
5119  # Special case; width and height come in one variable together
5120  if ( $type === 'handler' && $paramName === 'width' ) {
5121  $parsedWidthParam = $this->parseWidthParam( $value );
5122  if ( isset( $parsedWidthParam['width'] ) ) {
5123  $width = $parsedWidthParam['width'];
5124  if ( $handler->validateParam( 'width', $width ) ) {
5125  $params[$type]['width'] = $width;
5126  $validated = true;
5127  }
5128  }
5129  if ( isset( $parsedWidthParam['height'] ) ) {
5130  $height = $parsedWidthParam['height'];
5131  if ( $handler->validateParam( 'height', $height ) ) {
5132  $params[$type]['height'] = $height;
5133  $validated = true;
5134  }
5135  }
5136  # else no validation -- bug 13436
5137  } else {
5138  if ( $type === 'handler' ) {
5139  # Validate handler parameter
5140  $validated = $handler->validateParam( $paramName, $value );
5141  } else {
5142  # Validate internal parameters
5143  switch ( $paramName ) {
5144  case 'manualthumb':
5145  case 'alt':
5146  case 'class':
5147  # @todo FIXME: Possibly check validity here for
5148  # manualthumb? downstream behavior seems odd with
5149  # missing manual thumbs.
5150  $validated = true;
5151  $value = $this->stripAltText( $value, $holders );
5152  break;
5153  case 'link':
5154  $chars = self::EXT_LINK_URL_CLASS;
5155  $addr = self::EXT_LINK_ADDR;
5156  $prots = $this->mUrlProtocols;
5157  if ( $value === '' ) {
5158  $paramName = 'no-link';
5159  $value = true;
5160  $validated = true;
5161  } elseif ( preg_match( "/^((?i)$prots)/", $value ) ) {
5162  if ( preg_match( "/^((?i)$prots)$addr$chars*$/u", $value, $m ) ) {
5163  $paramName = 'link-url';
5164  $this->mOutput->addExternalLink( $value );
5165  if ( $this->mOptions->getExternalLinkTarget() ) {
5166  $params[$type]['link-target'] = $this->mOptions->getExternalLinkTarget();
5167  }
5168  $validated = true;
5169  }
5170  } else {
5171  $linkTitle = Title::newFromText( $value );
5172  if ( $linkTitle ) {
5173  $paramName = 'link-title';
5174  $value = $linkTitle;
5175  $this->mOutput->addLink( $linkTitle );
5176  $validated = true;
5177  }
5178  }
5179  break;
5180  case 'frameless':
5181  case 'framed':
5182  case 'thumbnail':
5183  // use first appearing option, discard others.
5184  $validated = ! $seenformat;
5185  $seenformat = true;
5186  break;
5187  default:
5188  # Most other things appear to be empty or numeric...
5189  $validated = ( $value === false || is_numeric( trim( $value ) ) );
5190  }
5191  }
5192 
5193  if ( $validated ) {
5194  $params[$type][$paramName] = $value;
5195  }
5196  }
5197  }
5198  if ( !$validated ) {
5199  $caption = $part;
5200  }
5201  }
5202 
5203  # Process alignment parameters
5204  if ( $params['horizAlign'] ) {
5205  $params['frame']['align'] = key( $params['horizAlign'] );
5206  }
5207  if ( $params['vertAlign'] ) {
5208  $params['frame']['valign'] = key( $params['vertAlign'] );
5209  }
5210 
5211  $params['frame']['caption'] = $caption;
5212 
5213  # Will the image be presented in a frame, with the caption below?
5214  $imageIsFramed = isset( $params['frame']['frame'] )
5215  || isset( $params['frame']['framed'] )
5216  || isset( $params['frame']['thumbnail'] )
5217  || isset( $params['frame']['manualthumb'] );
5218 
5219  # In the old days, [[Image:Foo|text...]] would set alt text. Later it
5220  # came to also set the caption, ordinary text after the image -- which
5221  # makes no sense, because that just repeats the text multiple times in
5222  # screen readers. It *also* came to set the title attribute.
5223  # Now that we have an alt attribute, we should not set the alt text to
5224  # equal the caption: that's worse than useless, it just repeats the
5225  # text. This is the framed/thumbnail case. If there's no caption, we
5226  # use the unnamed parameter for alt text as well, just for the time be-
5227  # ing, if the unnamed param is set and the alt param is not.
5228  # For the future, we need to figure out if we want to tweak this more,
5229  # e.g., introducing a title= parameter for the title; ignoring the un-
5230  # named parameter entirely for images without a caption; adding an ex-
5231  # plicit caption= parameter and preserving the old magic unnamed para-
5232  # meter for BC; ...
5233  if ( $imageIsFramed ) { # Framed image
5234  if ( $caption === '' && !isset( $params['frame']['alt'] ) ) {
5235  # No caption or alt text, add the filename as the alt text so
5236  # that screen readers at least get some description of the image
5237  $params['frame']['alt'] = $title->getText();
5238  }
5239  # Do not set $params['frame']['title'] because tooltips don't make sense
5240  # for framed images
5241  } else { # Inline image
5242  if ( !isset( $params['frame']['alt'] ) ) {
5243  # No alt text, use the "caption" for the alt text
5244  if ( $caption !== '' ) {
5245  $params['frame']['alt'] = $this->stripAltText( $caption, $holders );
5246  } else {
5247  # No caption, fall back to using the filename for the
5248  # alt text
5249  $params['frame']['alt'] = $title->getText();
5250  }
5251  }
5252  # Use the "caption" for the tooltip text
5253  $params['frame']['title'] = $this->stripAltText( $caption, $holders );
5254  }
5255 
5256  Hooks::run( 'ParserMakeImageParams', [ $title, $file, &$params, $this ] );
5257 
5258  # Linker does the rest
5259  $time = isset( $options['time'] ) ? $options['time'] : false;
5260  $ret = Linker::makeImageLink( $this, $title, $file, $params['frame'], $params['handler'],
5261  $time, $descQuery, $this->mOptions->getThumbSize() );
5262 
5263  # Give the handler a chance to modify the parser object
5264  if ( $handler ) {
5265  $handler->parserTransformHook( $this, $file );
5266  }
5267 
5268  return $ret;
5269  }
5270 
5276  protected function stripAltText( $caption, $holders ) {
5277  # Strip bad stuff out of the title (tooltip). We can't just use
5278  # replaceLinkHoldersText() here, because if this function is called
5279  # from replaceInternalLinks2(), mLinkHolders won't be up-to-date.
5280  if ( $holders ) {
5281  $tooltip = $holders->replaceText( $caption );
5282  } else {
5283  $tooltip = $this->replaceLinkHoldersText( $caption );
5284  }
5285 
5286  # make sure there are no placeholders in thumbnail attributes
5287  # that are later expanded to html- so expand them now and
5288  # remove the tags
5289  $tooltip = $this->mStripState->unstripBoth( $tooltip );
5290  $tooltip = Sanitizer::stripAllTags( $tooltip );
5291 
5292  return $tooltip;
5293  }
5294 
5300  public function disableCache() {
5301  wfDebug( "Parser output marked as uncacheable.\n" );
5302  if ( !$this->mOutput ) {
5303  throw new MWException( __METHOD__ .
5304  " can only be called when actually parsing something" );
5305  }
5306  $this->mOutput->updateCacheExpiry( 0 ); // new style, for consistency
5307  }
5308 
5317  public function attributeStripCallback( &$text, $frame = false ) {
5318  $text = $this->replaceVariables( $text, $frame );
5319  $text = $this->mStripState->unstripBoth( $text );
5320  return $text;
5321  }
5322 
5328  public function getTags() {
5329  return array_merge(
5330  array_keys( $this->mTransparentTagHooks ),
5331  array_keys( $this->mTagHooks ),
5332  array_keys( $this->mFunctionTagHooks )
5333  );
5334  }
5335 
5346  public function replaceTransparentTags( $text ) {
5347  $matches = [];
5348  $elements = array_keys( $this->mTransparentTagHooks );
5349  $text = self::extractTagsAndParams( $elements, $text, $matches );
5350  $replacements = [];
5351 
5352  foreach ( $matches as $marker => $data ) {
5353  list( $element, $content, $params, $tag ) = $data;
5354  $tagName = strtolower( $element );
5355  if ( isset( $this->mTransparentTagHooks[$tagName] ) ) {
5356  $output = call_user_func_array(
5357  $this->mTransparentTagHooks[$tagName],
5358  [ $content, $params, $this ]
5359  );
5360  } else {
5361  $output = $tag;
5362  }
5363  $replacements[$marker] = $output;
5364  }
5365  return strtr( $text, $replacements );
5366  }
5367 
5397  private function extractSections( $text, $sectionId, $mode, $newText = '' ) {
5398  global $wgTitle; # not generally used but removes an ugly failure mode
5399 
5400  $magicScopeVariable = $this->lock();
5401  $this->startParse( $wgTitle, new ParserOptions, self::OT_PLAIN, true );
5402  $outText = '';
5403  $frame = $this->getPreprocessor()->newFrame();
5404 
5405  # Process section extraction flags
5406  $flags = 0;
5407  $sectionParts = explode( '-', $sectionId );
5408  $sectionIndex = array_pop( $sectionParts );
5409  foreach ( $sectionParts as $part ) {
5410  if ( $part === 'T' ) {
5411  $flags |= self::PTD_FOR_INCLUSION;
5412  }
5413  }
5414 
5415  # Check for empty input
5416  if ( strval( $text ) === '' ) {
5417  # Only sections 0 and T-0 exist in an empty document
5418  if ( $sectionIndex == 0 ) {
5419  if ( $mode === 'get' ) {
5420  return '';
5421  } else {
5422  return $newText;
5423  }
5424  } else {
5425  if ( $mode === 'get' ) {
5426  return $newText;
5427  } else {
5428  return $text;
5429  }
5430  }
5431  }
5432 
5433  # Preprocess the text
5434  $root = $this->preprocessToDom( $text, $flags );
5435 
5436  # <h> nodes indicate section breaks
5437  # They can only occur at the top level, so we can find them by iterating the root's children
5438  $node = $root->getFirstChild();
5439 
5440  # Find the target section
5441  if ( $sectionIndex == 0 ) {
5442  # Section zero doesn't nest, level=big
5443  $targetLevel = 1000;
5444  } else {
5445  while ( $node ) {
5446  if ( $node->getName() === 'h' ) {
5447  $bits = $node->splitHeading();
5448  if ( $bits['i'] == $sectionIndex ) {
5449  $targetLevel = $bits['level'];
5450  break;
5451  }
5452  }
5453  if ( $mode === 'replace' ) {
5454  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5455  }
5456  $node = $node->getNextSibling();
5457  }
5458  }
5459 
5460  if ( !$node ) {
5461  # Not found
5462  if ( $mode === 'get' ) {
5463  return $newText;
5464  } else {
5465  return $text;
5466  }
5467  }
5468 
5469  # Find the end of the section, including nested sections
5470  do {
5471  if ( $node->getName() === 'h' ) {
5472  $bits = $node->splitHeading();
5473  $curLevel = $bits['level'];
5474  if ( $bits['i'] != $sectionIndex && $curLevel <= $targetLevel ) {
5475  break;
5476  }
5477  }
5478  if ( $mode === 'get' ) {
5479  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5480  }
5481  $node = $node->getNextSibling();
5482  } while ( $node );
5483 
5484  # Write out the remainder (in replace mode only)
5485  if ( $mode === 'replace' ) {
5486  # Output the replacement text
5487  # Add two newlines on -- trailing whitespace in $newText is conventionally
5488  # stripped by the editor, so we need both newlines to restore the paragraph gap
5489  # Only add trailing whitespace if there is newText
5490  if ( $newText != "" ) {
5491  $outText .= $newText . "\n\n";
5492  }
5493 
5494  while ( $node ) {
5495  $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG );
5496  $node = $node->getNextSibling();
5497  }
5498  }
5499 
5500  if ( is_string( $outText ) ) {
5501  # Re-insert stripped tags
5502  $outText = rtrim( $this->mStripState->unstripBoth( $outText ) );
5503  }
5504 
5505  return $outText;
5506  }
5507 
5522  public function getSection( $text, $sectionId, $defaultText = '' ) {
5523  return $this->extractSections( $text, $sectionId, 'get', $defaultText );
5524  }
5525 
5538  public function replaceSection( $oldText, $sectionId, $newText ) {
5539  return $this->extractSections( $oldText, $sectionId, 'replace', $newText );
5540  }
5541 
5547  public function getRevisionId() {
5548  return $this->mRevisionId;
5549  }
5550 
5557  public function getRevisionObject() {
5558  if ( !is_null( $this->mRevisionObject ) ) {
5559  return $this->mRevisionObject;
5560  }
5561  if ( is_null( $this->mRevisionId ) ) {
5562  return null;
5563  }
5564 
5565  $rev = call_user_func(
5566  $this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this
5567  );
5568 
5569  # If the parse is for a new revision, then the callback should have
5570  # already been set to force the object and should match mRevisionId.
5571  # If not, try to fetch by mRevisionId for sanity.
5572  if ( $rev && $rev->getId() != $this->mRevisionId ) {
5573  $rev = Revision::newFromId( $this->mRevisionId );
5574  }
5575 
5576  $this->mRevisionObject = $rev;
5577 
5578  return $this->mRevisionObject;
5579  }
5580 
5586  public function getRevisionTimestamp() {
5587  if ( is_null( $this->mRevisionTimestamp ) ) {
5589 
5590  $revObject = $this->getRevisionObject();
5591  $timestamp = $revObject ? $revObject->getTimestamp() : wfTimestampNow();
5592 
5593  # The cryptic '' timezone parameter tells to use the site-default
5594  # timezone offset instead of the user settings.
5595  # Since this value will be saved into the parser cache, served
5596  # to other users, and potentially even used inside links and such,
5597  # it needs to be consistent for all visitors.
5598  $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
5599 
5600  }
5601  return $this->mRevisionTimestamp;
5602  }
5603 
5609  public function getRevisionUser() {
5610  if ( is_null( $this->mRevisionUser ) ) {
5611  $revObject = $this->getRevisionObject();
5612 
5613  # if this template is subst: the revision id will be blank,
5614  # so just use the current user's name
5615  if ( $revObject ) {
5616  $this->mRevisionUser = $revObject->getUserText();
5617  } elseif ( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
5618  $this->mRevisionUser = $this->getUser()->getName();
5619  }
5620  }
5621  return $this->mRevisionUser;
5622  }
5623 
5629  public function getRevisionSize() {
5630  if ( is_null( $this->mRevisionSize ) ) {
5631  $revObject = $this->getRevisionObject();
5632 
5633  # if this variable is subst: the revision id will be blank,
5634  # so just use the parser input size, because the own substituation
5635  # will change the size.
5636  if ( $revObject ) {
5637  $this->mRevisionSize = $revObject->getSize();
5638  } else {
5639  $this->mRevisionSize = $this->mInputSize;
5640  }
5641  }
5642  return $this->mRevisionSize;
5643  }
5644 
5650  public function setDefaultSort( $sort ) {
5651  $this->mDefaultSort = $sort;
5652  $this->mOutput->setProperty( 'defaultsort', $sort );
5653  }
5654 
5665  public function getDefaultSort() {
5666  if ( $this->mDefaultSort !== false ) {
5667  return $this->mDefaultSort;
5668  } else {
5669  return '';
5670  }
5671  }
5672 
5679  public function getCustomDefaultSort() {
5680  return $this->mDefaultSort;
5681  }
5682 
5692  public function guessSectionNameFromWikiText( $text ) {
5693  # Strip out wikitext links(they break the anchor)
5694  $text = $this->stripSectionName( $text );
5696  return '#' . Sanitizer::escapeId( $text, 'noninitial' );
5697  }
5698 
5707  public function guessLegacySectionNameFromWikiText( $text ) {
5708  # Strip out wikitext links(they break the anchor)
5709  $text = $this->stripSectionName( $text );
5711  return '#' . Sanitizer::escapeId( $text, [ 'noninitial', 'legacy' ] );
5712  }
5713 
5728  public function stripSectionName( $text ) {
5729  # Strip internal link markup
5730  $text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
5731  $text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
5732 
5733  # Strip external link markup
5734  # @todo FIXME: Not tolerant to blank link text
5735  # I.E. [https://www.mediawiki.org] will render as [1] or something depending
5736  # on how many empty links there are on the page - need to figure that out.
5737  $text = preg_replace( '/\[(?i:' . $this->mUrlProtocols . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
5738 
5739  # Parse wikitext quotes (italics & bold)
5740  $text = $this->doQuotes( $text );
5741 
5742  # Strip HTML tags
5743  $text = StringUtils::delimiterReplace( '<', '>', '', $text );
5744  return $text;
5745  }
5746 
5757  public function testSrvus( $text, Title $title, ParserOptions $options,
5758  $outputType = self::OT_HTML
5759  ) {
5760  $magicScopeVariable = $this->lock();
5761  $this->startParse( $title, $options, $outputType, true );
5762 
5763  $text = $this->replaceVariables( $text );
5764  $text = $this->mStripState->unstripBoth( $text );
5765  $text = Sanitizer::removeHTMLtags( $text );
5766  return $text;
5767  }
5768 
5775  public function testPst( $text, Title $title, ParserOptions $options ) {
5776  return $this->preSaveTransform( $text, $title, $options->getUser(), $options );
5777  }
5778 
5785  public function testPreprocess( $text, Title $title, ParserOptions $options ) {
5786  return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
5787  }
5788 
5805  public function markerSkipCallback( $s, $callback ) {
5806  $i = 0;
5807  $out = '';
5808  while ( $i < strlen( $s ) ) {
5809  $markerStart = strpos( $s, self::MARKER_PREFIX, $i );
5810  if ( $markerStart === false ) {
5811  $out .= call_user_func( $callback, substr( $s, $i ) );
5812  break;
5813  } else {
5814  $out .= call_user_func( $callback, substr( $s, $i, $markerStart - $i ) );
5815  $markerEnd = strpos( $s, self::MARKER_SUFFIX, $markerStart );
5816  if ( $markerEnd === false ) {
5817  $out .= substr( $s, $markerStart );
5818  break;
5819  } else {
5820  $markerEnd += strlen( self::MARKER_SUFFIX );
5821  $out .= substr( $s, $markerStart, $markerEnd - $markerStart );
5822  $i = $markerEnd;
5823  }
5824  }
5825  }
5826  return $out;
5827  }
5828 
5835  public function killMarkers( $text ) {
5836  return $this->mStripState->killMarkers( $text );
5837  }
5838 
5855  public function serializeHalfParsedText( $text ) {
5856  $data = [
5857  'text' => $text,
5858  'version' => self::HALF_PARSED_VERSION,
5859  'stripState' => $this->mStripState->getSubState( $text ),
5860  'linkHolders' => $this->mLinkHolders->getSubArray( $text )
5861  ];
5862  return $data;
5863  }
5864 
5880  public function unserializeHalfParsedText( $data ) {
5881  if ( !isset( $data['version'] ) || $data['version'] != self::HALF_PARSED_VERSION ) {
5882  throw new MWException( __METHOD__ . ': invalid version' );
5883  }
5884 
5885  # First, extract the strip state.
5886  $texts = [ $data['text'] ];
5887  $texts = $this->mStripState->merge( $data['stripState'], $texts );
5888 
5889  # Now renumber links
5890  $texts = $this->mLinkHolders->mergeForeign( $data['linkHolders'], $texts );
5891 
5892  # Should be good to go.
5893  return $texts[0];
5894  }
5895 
5905  public function isValidHalfParsedText( $data ) {
5906  return isset( $data['version'] ) && $data['version'] == self::HALF_PARSED_VERSION;
5907  }
5908 
5917  public function parseWidthParam( $value ) {
5918  $parsedWidthParam = [];
5919  if ( $value === '' ) {
5920  return $parsedWidthParam;
5921  }
5922  $m = [];
5923  # (bug 13500) In both cases (width/height and width only),
5924  # permit trailing "px" for backward compatibility.
5925  if ( preg_match( '/^([0-9]*)x([0-9]*)\s*(?:px)?\s*$/', $value, $m ) ) {
5926  $width = intval( $m[1] );
5927  $height = intval( $m[2] );
5928  $parsedWidthParam['width'] = $width;
5929  $parsedWidthParam['height'] = $height;
5930  } elseif ( preg_match( '/^[0-9]*\s*(?:px)?\s*$/', $value ) ) {
5931  $width = intval( $value );
5932  $parsedWidthParam['width'] = $width;
5933  }
5934  return $parsedWidthParam;
5935  }
5936 
5946  protected function lock() {
5947  if ( $this->mInParse ) {
5948  throw new MWException( "Parser state cleared while parsing. "
5949  . "Did you call Parser::parse recursively?" );
5950  }
5951  $this->mInParse = true;
5952 
5953  $recursiveCheck = new ScopedCallback( function() {
5954  $this->mInParse = false;
5955  } );
5956 
5957  return $recursiveCheck;
5958  }
5959 
5970  public static function stripOuterParagraph( $html ) {
5971  $m = [];
5972  if ( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $html, $m ) ) {
5973  if ( strpos( $m[1], '</p>' ) === false ) {
5974  $html = $m[1];
5975  }
5976  }
5977 
5978  return $html;
5979  }
5980 
5991  public function getFreshParser() {
5992  global $wgParserConf;
5993  if ( $this->mInParse ) {
5994  return new $wgParserConf['class']( $wgParserConf );
5995  } else {
5996  return $this;
5997  }
5998  }
5999 
6006  public function enableOOUI() {
6008  $this->mOutput->setEnableOOUI( true );
6009  }
6010 }
getRevisionObject()
Get the revision object for $this->mRevisionId.
Definition: Parser.php:5557
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:522
setTitle($t)
Set the context title.
Definition: Parser.php:719
$mAutonumber
Definition: Parser.php:176
markerSkipCallback($s, $callback)
Call a callback function on all regions of the given text that are not inside strip markers...
Definition: Parser.php:5805
#define the
table suitable for use with IDatabase::select()
$mPPNodeCount
Definition: Parser.php:190
replaceInternalLinks2(&$s)
Process [[ ]] wikilinks (RIL)
Definition: Parser.php:2055
static getVariableIDs()
Get an array of parser variable IDs.
Definition: MagicWord.php:271
you don t have to do a grep find to see where the $wgReverseTitle variable is used
Definition: hooks.txt:117
getExternalLinkAttribs($url)
Get an associative array of additional HTML attributes appropriate for a particular external link...
Definition: Parser.php:1882
const MARKER_PREFIX
Definition: Parser.php:133
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
isValidHalfParsedText($data)
Returns true if the given array, presumed to be generated by serializeHalfParsedText(), is compatible with the current version of the parser.
Definition: Parser.php:5905
null means default in associative array form
Definition: hooks.txt:1816
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:1816
static tocLineEnd()
End a Table Of Contents line.
Definition: Linker.php:1634
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
getSection($text, $sectionId, $defaultText= '')
This function returns the text of a section, specified by a number ($section).
Definition: Parser.php:5522
static decodeTagAttributes($text)
Return an associative array of attribute names and values from a partial tag string.
Definition: Sanitizer.php:1286
$mTplRedirCache
Definition: Parser.php:192
killMarkers($text)
Remove any strip markers found in the given text.
Definition: Parser.php:5835
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
static tocList($toc, $lang=false)
Wraps the TOC in a table and provides the hide/collapse javascript.
Definition: Linker.php:1646
LinkRenderer $mLinkRenderer
Definition: Parser.php:256
fetchTemplateAndTitle($title)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3464
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:776
getRevisionUser()
Get the name of the user that edited the last revision.
Definition: Parser.php:5609
setFunctionTagHook($tag, $callback, $flags)
Create a tag function, e.g.
Definition: Parser.php:4804
the array() calling protocol came about after MediaWiki 1.4rc1.
stripSectionName($text)
Strips a text string of wikitext for use in a section anchor.
Definition: Parser.php:5728
const OT_PREPROCESS
Definition: Defines.php:229
$mDoubleUnderscores
Definition: Parser.php:192
Group all the pieces relevant to the context of a request into one instance.
getPreloadText($text, Title $title, ParserOptions $options, $params=[])
Process the wikitext for the "?preload=" feature.
Definition: Parser.php:667
$context
Definition: load.php:43
validateSig($text)
Check that the user's signature contains no bad XML.
Definition: Parser.php:4518
MapCacheLRU null $currentRevisionCache
Definition: Parser.php:242
$wgSitename
Name of the site.
renderImageGallery($text, $params)
Renders an image gallery from a text with one line per image.
Definition: Parser.php:4855
recursivePreprocess($text, $frame=false)
Recursive parser entry point that can be called from an extension tag hook.
Definition: Parser.php:648
replaceExternalLinks($text)
Replace external links (REL)
Definition: Parser.php:1782
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:189
static isNonincludable($index)
It is not possible to use pages from this namespace as template?
nextLinkID()
Definition: Parser.php:808
const SPACE_NOT_NL
Definition: Parser.php:102
static replaceUnusualEscapes($url)
Replace unusual escape codes in a URL with their equivalent characters.
Definition: Parser.php:1910
getImageParams($handler)
Definition: Parser.php:5013
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
doHeadings($text)
Parse headers and return html.
Definition: Parser.php:1561
static getTitleFor($name, $subpage=false, $fragment= '')
Get a localised Title object for a specified special page name.
Definition: SpecialPage.php:80
const OT_PLAIN
Definition: Parser.php:113
getTags()
Accessor.
Definition: Parser.php:5328
static isWellFormedXmlFragment($text)
Check if a string is a well-formed XML fragment.
Definition: Xml.php:735
const OT_WIKI
Definition: Parser.php:110
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException'returning false will NOT prevent logging $e
Definition: hooks.txt:1980
fetchFileAndTitle($title, $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3606
User $mUser
Definition: Parser.php:199
initialiseVariables()
initialise the magic variables (like CURRENTMONTHNAME) and substitution modifiers ...
Definition: Parser.php:2793
static isEnabled()
Definition: MWTidy.php:79
Set options of the Parser.
static tidy($text)
Interface with html tidy.
Definition: MWTidy.php:46
getFunctionHooks()
Get all registered function hook identifiers.
Definition: Parser.php:4790
static fixTagAttributes($text, $element, $sorted=false)
Take a tag soup fragment listing an HTML element's attributes and normalize it to well-formed XML...
Definition: Sanitizer.php:1070
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
database rows
Definition: globals.txt:10
wfHostname()
Fetch server name for use in error reporting etc.
getFunctionLang()
Get a language object for use in parser functions such as {{FORMATNUM:}}.
Definition: Parser.php:823
argSubstitution($piece, $frame)
Triple brace replacement – used for template arguments.
Definition: Parser.php:3709
testSrvus($text, Title $title, ParserOptions $options, $outputType=self::OT_HTML)
strip/replaceVariables/unstrip for preprocessor regression testing
Definition: Parser.php:5757
uniqPrefix()
Accessor for mUniqPrefix.
Definition: Parser.php:709
const TOC_START
Definition: Parser.php:136
Title($x=null)
Accessor/mutator for the Title object.
Definition: Parser.php:747
SectionProfiler $mProfiler
Definition: Parser.php:251
$wgEnableScaryTranscluding
Enable interwiki transcluding.
$sort
fetchFileNoRegister($title, $options=[])
Helper function for fetchFileAndTitle.
Definition: Parser.php:3631
null for the local wiki Added in
Definition: hooks.txt:1435
There are three types of nodes:
$mHeadings
Definition: Parser.php:192
$value
clearTagHooks()
Remove all tag hooks.
Definition: Parser.php:4703
static makeSelfLinkObj($nt, $html= '', $query= '', $trail= '', $prefix= '')
Make appropriate markup for a link to the current article.
Definition: Linker.php:277
const NS_SPECIAL
Definition: Defines.php:58
clearState()
Clear Parser state.
Definition: Parser.php:340
__construct($conf=[])
Definition: Parser.php:261
const EXT_LINK_ADDR
Definition: Parser.php:94
$mFirstCall
Definition: Parser.php:151
interwikiTransclude($title, $action)
Transclude an interwiki link.
Definition: Parser.php:3650
pstPass2($text, $user)
Pre-save transform helper function.
Definition: Parser.php:4390
guessLegacySectionNameFromWikiText($text)
Same as guessSectionNameFromWikiText(), but produces legacy anchors instead.
Definition: Parser.php:5707
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
wfUrlProtocolsWithoutProtRel()
Like wfUrlProtocols(), but excludes '//' from the protocol list.
Options($x=null)
Accessor/mutator for the ParserOptions object.
Definition: Parser.php:801
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2588
serializeHalfParsedText($text)
Save the parser state required to convert the given half-parsed text to HTML.
Definition: Parser.php:5855
replaceLinkHolders(&$text, $options=0)
Replace "<!--LINK-->" link placeholders with actual links, in the buffer Placeholders created in Link...
Definition: Parser.php:4827
static activeUsers()
Definition: SiteStats.php:161
$mLinkID
Definition: Parser.php:189
doQuotes($text)
Helper function for doAllQuotes()
Definition: Parser.php:1594
preprocessToDom($text, $flags=0)
Preprocess some wikitext and return the document tree.
Definition: Parser.php:2823
limitationWarn($limitationType, $current= '', $max= '')
Warn the user when a parser limitation is reached Will warn at most once the user per limitation type...
Definition: Parser.php:2945
static cleanUrl($url)
Definition: Sanitizer.php:1855
wfUrlencode($s)
We want some things to be included as literal characters in our title URLs for prettiness, which urlencode encodes by default.
static newFromText($text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:256
$mGeneratedPPNodeCount
Definition: Parser.php:190
Represents a title within MediaWiki.
Definition: Title.php:36
static getRandomString()
Get a random string.
Definition: Parser.php:688
$mRevisionId
Definition: Parser.php:216
static stripAllTags($text)
Take a fragment of (potentially invalid) HTML and return a version with any tags removed, encoded as plain text.
Definition: Sanitizer.php:1822
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
doBlockLevels($text, $linestart)
Make lists from lines starting with ':', '*', '#', etc.
Definition: Parser.php:2426
$wgArticlePath
Definition: img_auth.php:45
OutputType($x=null)
Accessor/mutator for the output type.
Definition: Parser.php:773
getLinkRenderer()
Get a LinkRenderer instance to make links with.
Definition: Parser.php:890
const NS_TEMPLATE
Definition: Defines.php:79
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target...
Definition: Revision.php:117
getVariableValue($index, $frame=false)
Return value of a magic variable (like PAGENAME)
Definition: Parser.php:2441
recursiveTagParse($text, $frame=false)
Half-parse wikitext to half-parsed HTML.
Definition: Parser.php:583
const NO_ARGS
magic word & $parser
Definition: hooks.txt:2372
MagicWordArray $mVariables
Definition: Parser.php:158
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:748
const SFH_NO_HASH
Definition: Parser.php:84
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
wfRandomString($length=32)
Get a random string containing a number of pseudo-random hex characters.
$mForceTocPosition
Definition: Parser.php:194
preprocess($text, Title $title=null, ParserOptions $options, $revid=null, $frame=false)
Expand templates and variables in the text, producing valid, static wikitext.
Definition: Parser.php:624
static getCacheTTL($id)
Allow external reads of TTL array.
Definition: MagicWord.php:294
getRevisionId()
Get the ID of the revision we are parsing.
Definition: Parser.php:5547
const OT_PREPROCESS
Definition: Parser.php:111
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1629
maybeDoSubpageLink($target, &$text)
Handle link to subpage if necessary.
Definition: Parser.php:2414
$mFunctionSynonyms
Definition: Parser.php:143
If you want to remove the page from your watchlist later
replaceLinkHoldersText($text)
Replace "<!--LINK-->" link placeholders with plain text of links (not HTML-formatted).
Definition: Parser.php:4838
setLinkID($id)
Definition: Parser.php:815
$mOutputType
Definition: Parser.php:213
wfDebug($text, $dest= 'all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
$mDefaultStripList
Definition: Parser.php:146
static createAssocArgs($args)
Clean up argument array - refactored in 1.9 so parserfunctions can use it, too.
Definition: Parser.php:2897
$mExtLinkBracketedRegex
Definition: Parser.php:165
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.Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page.Return false to stop further processing of the tag $reader:XMLReader object &$pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision.Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag.Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload.Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports.&$fullInterwikiPrefix:Interwiki prefix, may contain colons.&$pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable.Can be used to lazy-load the import sources list.&$importSources:The value of $wgImportSources.Modify as necessary.See the comment in DefaultSettings.php for the detail of how to structure this array. '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 '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 '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 '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. '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 IP::isTrustedProxy() &$ip:IP being check &$result:Change this value to override the result of IP::isTrustedProxy() '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 Sanitizer::validateEmail(), 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. '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) '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 '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. 'LanguageSelector':Hook to change the language selector available on a page.$out:The output page.$cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED!Use HtmlPageLinkRendererBegin instead.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:1814
if($line===false) $args
Definition: cdb.php:64
the value to return A Title object or null for latest to be modified or replaced by the hook handler or if authentication is not possible after cache objects are set for highlighting & $link
Definition: hooks.txt:2621
static getLocalInstance($ts=false)
Get a timestamp instance in the server local timezone ($wgLocaltimezone)
$wgMaxSigChars
Maximum number of Unicode characters in signature.
static getDoubleUnderscoreArray()
Get a MagicWordArray of double-underscore entities.
Definition: MagicWord.php:307
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:1721
getTemplateDom($title)
Get the semi-parsed DOM representation of a template with a given title, and its redirect destination...
Definition: Parser.php:3389
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:47
static decodeCharReferences($text)
Decode any character references, numeric or named entities, in the text and return a UTF-8 string...
Definition: Sanitizer.php:1499
cleanSig($text, $parsing=false)
Clean up signature text.
Definition: Parser.php:4532
wfTimestamp($outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
$wgNoFollowNsExceptions
Namespaces in which $wgNoFollowLinks doesn't apply.
static factory($mode=false, IContextSource $context=null)
Get a new image gallery.
$wgLanguageCode
Site language code.
Custom PHP profiler for parser/DB type section names that xhprof/xdebug can't handle.
static getPage($name)
Find the object with a given name and return it (or NULL)
static edits()
Definition: SiteStats.php:129
Class for asserting that a callback happens when an dummy object leaves scope.
$wgExtraInterlanguageLinkPrefixes
List of additional interwiki prefixes that should be treated as interlanguage links (i...
startExternalParse(Title $title=null, ParserOptions $options, $outputType, $clearState=true)
Set up some variables which are usually set up in parse() so that an external function can call some ...
Definition: Parser.php:4583
wfDebugLog($logGroup, $text, $dest= 'all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not...
const NO_TEMPLATES
addTrackingCategory($msg)
Definition: Parser.php:3928
replaceInternalLinks($s)
Process [[ ]] wikilinks.
Definition: Parser.php:2042
$mVarCache
Definition: Parser.php:147
$wgStylePath
The URL path of the skins directory.
disableCache()
Set a flag in the output object indicating that the content is dynamic and shouldn't be cached...
Definition: Parser.php:5300
$mRevisionObject
Definition: Parser.php:215
static normalizeSectionNameWhitespace($section)
Normalizes whitespace in a section name, such as might be returned by Parser::stripSectionName(), for use in the id's that are used for section links.
Definition: Sanitizer.php:1380
internalParse($text, $isMain=true, $frame=false)
Helper function for parse() that transforms wiki markup into half-parsed HTML.
Definition: Parser.php:1223
Title $mTitle
Definition: Parser.php:212
__destruct()
Reduce memory usage to reduce the impact of circular references.
Definition: Parser.php:287
wfEscapeWikiText($text)
Escapes the given text so that it may be output using addWikiText() without any linking, formatting, etc.
bool $mInParse
Recursive call protection.
Definition: Parser.php:248
Some quick notes on the file repository architecture Functionality is
Definition: README:3
getRevisionTimestamp()
Get the timestamp associated with the current revision, adjusted for the default server-local timesta...
Definition: Parser.php:5586
Class that generates HTML links for pages.
static stripOuterParagraph($html)
Strip outer.
Definition: Parser.php:5970
static register($parser)
$mRevIdForTs
Definition: Parser.php:220
static singleton()
Get an instance of this class.
Definition: LinkCache.php:65
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
static normalizeSubpageLink($contextTitle, $target, &$text)
Definition: Linker.php:1440
parseWidthParam($value)
Parsed a width param of imagelink like 300px or 200x300px.
Definition: Parser.php:5917
$mStripList
Definition: Parser.php:145
$mFunctionTagHooks
Definition: Parser.php:144
fetchScaryTemplateMaybeFromCache($url)
Definition: Parser.php:3669
const OT_PLAIN
Definition: Defines.php:231
$wgNoFollowLinks
If true, external URL links in wiki text will be given the rel="nofollow" attribute as a hint to sear...
fetchCurrentRevisionOfTitle($title)
Fetch the current revision of a given title.
Definition: Parser.php:3432
$mRevisionTimestamp
Definition: Parser.php:217
$mImageParams
Definition: Parser.php:148
stripAltText($caption, $holders)
Definition: Parser.php:5276
doAllQuotes($text)
Replace single quotes with HTML markup.
Definition: Parser.php:1577
static normalizeUrlComponent($component, $unsafe)
Definition: Parser.php:1960
if($limit) $timestamp
const VERSION
Update this version number when the ParserOutput format changes in an incompatible way...
Definition: Parser.php:75
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1020
setHook($tag, $callback)
Create an HTML-style tag, e.g.
Definition: Parser.php:4658
const OT_WIKI
Definition: Defines.php:228
Preprocessor $mPreprocessor
Definition: Parser.php:169
getPreprocessor()
Get a preprocessor object.
Definition: Parser.php:876
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
static getInstance($ts=false)
Get a timestamp instance in GMT.
const NS_MEDIA
Definition: Defines.php:57
static singleton()
Get a RepoGroup instance.
Definition: RepoGroup.php:59
replaceVariables($text, $frame=false, $argsOnly=false)
Replace magic variables, templates, and template arguments with the appropriate text.
Definition: Parser.php:2868
const RECOVER_ORIG
wfMatchesDomainList($url, $domains)
Check whether a given URL has a domain that occurs in a given set of domains.
MediaWiki exception.
Definition: MWException.php:26
StripState $mStripState
Definition: Parser.php:181
$mDefaultSort
Definition: Parser.php:191
getUser()
Get a User object either from $this->mUser, if set, or from the ParserOptions object otherwise...
Definition: Parser.php:864
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
incrementIncludeSize($type, $size)
Increment an include size counter.
Definition: Parser.php:3846
getStripList()
Get a list of strippable XML-like elements.
Definition: Parser.php:993
const EXT_IMAGE_REGEX
Definition: Parser.php:97
startParse(Title $title=null, ParserOptions $options, $outputType, $clearState=true)
Definition: Parser.php:4595
$params
const NS_CATEGORY
Definition: Defines.php:83
static makeHeadline($level, $attribs, $anchor, $html, $link, $legacyAnchor=false)
Create a headline for content.
Definition: Linker.php:1702
static extractTagsAndParams($elements, $text, &$matches, $uniq_prefix=null)
Replaces all occurrences of HTML-style comments and the given tags in the text with a random marker a...
Definition: Parser.php:923
and(b) You must cause any modified files to carry prominent notices stating that You changed the files
doTableStuff($text)
parse the wiki syntax used to render tables
Definition: Parser.php:1020
wfDeprecated($function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
getRevisionSize()
Get the size of the revision.
Definition: Parser.php:5629
$mImageParamsMagicArray
Definition: Parser.php:149
LinkHolderArray $mLinkHolders
Definition: Parser.php:187
$wgNoFollowDomainExceptions
If this is set to an array of domains, external links to these domain names (or any subdomains) will ...
static register($parser)
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
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to and or sell copies of the and to permit persons to whom the Software is furnished to do so
Definition: LICENSE.txt:10
$wgTranscludeCacheExpiry
Expiry time for transcluded templates cached in transcache database table.
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
const DB_SLAVE
Definition: Defines.php:46
preSaveTransform($text, Title $title, User $user, ParserOptions $options, $clearState=true)
Transform wiki markup when saving a page by doing "\\r\\n" -> "\\n" conversion, substituting signatur...
Definition: Parser.php:4358
static capturePath(Title $title, IContextSource $context, LinkRenderer $linkRenderer=null)
Just like executePath() but will override global variables and execute the page in "inclusion" mode...
getTargetLanguage()
Get the target language for the content being parsed.
Definition: Parser.php:836
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 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 after processing after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation 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 unsetoffset-wrap String Wrap the message in html(usually something like"&lt
$buffer
Allows to change the fields on the form that will be generated are created Can be used to omit specific feeds from being outputted You must not use this hook to add use OutputPage::addFeedLink() instead.&$feedLinks conditions will AND in the final query as a Content object as a Content object $title
Definition: hooks.txt:312
static hasSubpages($index)
Does the namespace allow subpages?
formatHeadings($text, $origText, $isMain=true)
This function accomplishes several tasks: 1) Auto-number headings if that option is enabled 2) Add an...
Definition: Parser.php:3948
getConverterLanguage()
Get the language object for language conversion.
Definition: Parser.php:854
static tocUnindent($level)
Finish one or more sublevels on the Table of Contents.
Definition: Linker.php:1601
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
static tocLine($anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition: Linker.php:1616
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
$mInputSize
Definition: Parser.php:221
magicword txt Magic Words are some phrases used in the wikitext They are used for two things
Definition: magicword.txt:4
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books $tag
Definition: hooks.txt:981
getUserSig(&$user, $nickname=false, $fancySig=null)
Fetch the user's signature text, if any, and normalize to validated, ready-to-insert wikitext...
Definition: Parser.php:4469
const HALF_PARSED_VERSION
Update this version number when the output of serialiseHalfParsedText() changes in an incompatible wa...
Definition: Parser.php:81
const NS_FILE
Definition: Defines.php:75
firstCallInit()
Do various kinds of initialisation on the first call of the parser.
Definition: Parser.php:322
Handles a simple LRU key/value map with a maximum number of entries.
Definition: MapCacheLRU.php:34
static makeImageLink(Parser $parser, Title $title, $file, $frameParams=[], $handlerParams=[], $time=false, $query="", $widthOption=null)
Given parameters derived from [[Image:Foo|options...]], generate the HTML that that syntax inserts in...
Definition: Linker.php:415
const PTD_FOR_INCLUSION
Definition: Parser.php:105
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:1816
armorLinks($text)
Insert a NOPARSE hacky thing into any inline links in a chunk that's going to go through further pars...
Definition: Parser.php:2392
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:1601
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:1816
static splitWhitespace($s)
Return a three-element array: leading whitespace, string contents, trailing whitespace.
Definition: Parser.php:2835
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
setOutputType($ot)
Set the output type.
Definition: Parser.php:756
$mTagHooks
Definition: Parser.php:140
Class for handling an array of magic words.
const NS_MEDIAWIKI
Definition: Defines.php:77
static & get($id)
Factory: creates an object representing an ID.
Definition: MagicWord.php:257
equals(Content $that=null)
Returns true if this Content objects is conceptually equivalent to the given Content object...
enableOOUI()
Set's up the PHP implementation of OOUI for use in this request and instructs OutputPage to enable OO...
Definition: Parser.php:6006
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 local account $user
Definition: hooks.txt:242
fetchTemplate($title)
Fetch the unparsed text of a template and register a reference to it.
Definition: Parser.php:3492
maybeMakeExternalImage($url)
make an image if it's allowed, either through the global option, through the exception, or through the on-wiki whitelist
Definition: Parser.php:1983
areSubpagesAllowed()
Return true if subpage links should be expanded on this page.
Definition: Parser.php:2401
const OT_HTML
Definition: Defines.php:227
static escapeId($id, $options=[])
Given a value, escape it so that it can be used in an id attribute and return it. ...
Definition: Sanitizer.php:1169
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object & $output
Definition: hooks.txt:1020
static getSubstIDs()
Get an array of parser substitution modifier IDs.
Definition: MagicWord.php:284
static images()
Definition: SiteStats.php:169
$mTransparentTagHooks
Definition: Parser.php:141
$mExpensiveFunctionCount
Definition: Parser.php:193
$mUrlProtocols
Definition: Parser.php:165
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
$mConf
Definition: Parser.php:165
transformMsg($text, $options, $title=null)
Wrapper for preprocess()
Definition: Parser.php:4614
static newFromId($id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:99
wfUrlProtocols($includeProtocolRelative=true)
Returns a regular expression of url protocols.
static makeExternalLink($url, $text, $escape=true, $linktype= '', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:936
__clone()
Allow extensions to clean up when the parser is cloned.
Definition: Parser.php:299
static getExternalLinkRel($url=false, $title=null)
Get the rel attribute for a particular external link.
Definition: Parser.php:1861
string $mUniqPrefix
Deprecated accessor for the strip marker prefix.
Definition: Parser.php:227
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
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...
this hook is for auditing only $req
Definition: hooks.txt:981
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:776
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc next in line in page history
Definition: hooks.txt:1601
array $mLangLinkLanguages
Array with the language name of each language link (i.e.
Definition: Parser.php:234
const OT_MSG
Definition: Parser.php:112
replaceTransparentTags($text)
Replace transparent tags in $text with the values given by the callbacks.
Definition: Parser.php:5346
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
replaceSection($oldText, $sectionId, $newText)
This function returns $oldtext after the content of the section specified by $section has been replac...
Definition: Parser.php:5538
doDoubleUnderscore($text)
Strip double-underscore items like NOGALLERY and NOTOC Fills $this->mDoubleUnderscores, returns the modified text.
Definition: Parser.php:3873
$mFunctionHooks
Definition: Parser.php:142
static removeHTMLtags($text, $processCallback=null, $args=[], $extratags=[], $removetags=[], $warnCallback=null)
Cleans up HTML, removes dangerous tags and attributes, and removes HTML comments. ...
Definition: Sanitizer.php:462
$lines
Definition: router.php:66
testPreprocess($text, Title $title, ParserOptions $options)
Definition: Parser.php:5785
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
MagicWordArray $mSubstWords
Definition: Parser.php:163
const TOC_END
Definition: Parser.php:137
static normalizeCharReferences($text)
Ensure that any entities and character references are legal for XML and XHTML specifically.
Definition: Sanitizer.php:1399
callParserFunction($frame, $function, array $args=[])
Call a parser function and return an array with text and flags.
Definition: Parser.php:3293
$wgScriptPath
The path we should point to.
Variant of the Message class.
Definition: Message.php:1236
getFreshParser()
Return this parser if it is not doing anything, otherwise get a fresh parser.
Definition: Parser.php:5991
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
WebRequest clone which takes values from a provided array.
Definition: FauxRequest.php:33
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content $content
Definition: hooks.txt:1020
static articles()
Definition: SiteStats.php:137
$mRevisionUser
Definition: Parser.php:218
lock()
Lock the current instance of the parser.
Definition: Parser.php:5946
static pages()
Definition: SiteStats.php:145
$line
Definition: cdb.php:59
const SFH_OBJECT_ARGS
Definition: Parser.php:85
makeKnownLinkHolder($nt, $text= '', $trail= '', $prefix= '')
Render a forced-blue link inline; protect against double expansion of URLs if we're in a mode that pr...
Definition: Parser.php:2368
static statelessFetchTemplate($title, $parser=false)
Static function to get a template Can be overridden via ParserOptions::setTemplateCallback().
Definition: Parser.php:3505
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
setFunctionHook($id, $callback, $flags=0)
Create a function, e.g.
Definition: Parser.php:4752
static setupOOUI($skinName= '', $dir= 'ltr')
Helper function to setup the PHP implementation of OOUI to use in this request.
static makeMediaLinkFile(Title $title, $file, $html= '')
Create a direct link to a given uploaded file.
Definition: Linker.php:876
$mIncludeCount
Definition: Parser.php:183
usually copyright or history_copyright This message must be in HTML not wikitext if the section is included from a template to be included in the link
Definition: hooks.txt:2755
$mMarkerIndex
Definition: Parser.php:150
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
Definition: hooks.txt:1020
getTitle()
Accessor for the Title object.
Definition: Parser.php:737
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 local content language as $wgContLang
Definition: design.txt:56
extractSections($text, $sectionId, $mode, $newText= '')
Break wikitext input into sections, and either pull or replace some particular section's text...
Definition: Parser.php:5397
ParserOutput $mOutput
Definition: Parser.php:175
getOutput()
Get the ParserOutput object.
Definition: Parser.php:782
$wgExperimentalHtmlIds
Should we allow a broader set of characters in id attributes, per HTML5? If not, use only HTML 4-comp...
static statelessFetchRevision($title, $parser=false)
Wrapper around Revision::newFromTitle to allow passing additional parameters without passing them on ...
Definition: Parser.php:3455
doMagicLinks($text)
Replace special strings like "ISBN xxx" and "RFC xxx" with magic external links.
Definition: Parser.php:1394
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for and distribution as defined by Sections through of this document Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License Legal Entity shall mean the union of the acting entity and all other entities that control are controlled by or are under common control with that entity For the purposes of this definition control direct or to cause the direction or management of such whether by contract or including but not limited to software source documentation and configuration files Object form shall mean any form resulting from mechanical transformation or translation of a Source including but not limited to compiled object generated and conversions to other media types Work shall mean the work of whether in Source or Object made available under the as indicated by a copyright notice that is included in or attached to the whether in Source or Object that is based or other modifications as a an original work of authorship For the purposes of this Derivative Works shall not include works that remain separable or merely the Work and Derivative Works thereof Contribution shall mean any work of including the original version of the Work and any modifications or additions to that Work or Derivative Works that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner For the purposes of this submitted means any form of or written communication sent to the Licensor or its including but not limited to communication on electronic mailing source code control and issue tracking systems that are managed or on behalf the Licensor for the purpose of discussing and improving the but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as Not a Contribution Contributor shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work Grant of Copyright License Subject to the terms and conditions of this each Contributor hereby grants to You a non no royalty irrevocable copyright license to prepare Derivative Works publicly display
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition: hooks.txt:1020
static cleanSigInSig($text)
Strip 3, 4 or 5 tildes out of signatures.
Definition: Parser.php:4569
setDefaultSort($sort)
Mutator for $mDefaultSort.
Definition: Parser.php:5650
fetchFile($title, $options=[])
Fetch a file and its title and register a reference to it.
Definition: Parser.php:3595
static tocIndent()
Add another level to the Table of Contents.
Definition: Linker.php:1590
static legalChars()
Get a regex character class describing the legal characters in a link.
Definition: Title.php:585
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
static doBlockLevels($text, $lineStart)
Make lists from lines starting with ':', '*', '#', etc.
$wgServer
URL of the server.
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
incrementExpensiveFunctionCount()
Increment the expensive function count.
Definition: Parser.php:3860
const DB_MASTER
Definition: Defines.php:47
$mShowToc
Definition: Parser.php:194
getLinkURL($query= '', $query2=false, $proto=false)
Get a URL that's the simplest URL that will be valid to link, locally, to the current Title...
Definition: Title.php:1781
static normalizeLinkUrl($url)
Replace unusual escape codes in a URL with their equivalent characters.
Definition: Parser.php:1924
magicLinkCallback($m)
Definition: Parser.php:1424
const EXT_LINK_URL_CLASS
Definition: Parser.php:91
insertStripItem($text)
Add an item to the strip state Returns the unique tag which must be inserted into the stripped text T...
Definition: Parser.php:1006
testPst($text, Title $title, ParserOptions $options)
Definition: Parser.php:5775
static factory($url, $options=null, $caller=__METHOD__)
Generate a new request object.
const TS_UNIX
Unix time - the number of seconds since 1970-01-01 00:00:00 UTC.
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:1816
if(!$wgRequest->checkUrlExtension()) if(!$wgEnableAPI) $wgTitle
Definition: api.php:57
ParserOptions $mOptions
Definition: Parser.php:207
parse($text, Title $title, ParserOptions $options, $linestart=true, $clearState=true, $revid=null)
Convert wikitext to HTML Do not call this function recursively.
Definition: Parser.php:398
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
Definition: hooks.txt:776
static numberingroup($group)
Find the number of users in a given user group.
Definition: SiteStats.php:179
=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() 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
const STRIP_COMMENTS
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:28
static getVersion($flags= '', $lang=null)
Return a string of the MediaWiki version with Git revision if available.
braceSubstitution($piece, $frame)
Return the text of a template, after recursively replacing any variables or templates within the temp...
Definition: Parser.php:2967
setUser($user)
Set the current user.
Definition: Parser.php:699
$mHighestExpansionDepth
Definition: Parser.php:190
makeImage($title, $options, $holders=false)
Parse image options text and use it to make an image.
Definition: Parser.php:5061
attributeStripCallback(&$text, $frame=false)
Callback from the Sanitizer for expanding items found in HTML attribute values, so they can be safely...
Definition: Parser.php:5317
static cascadingsources($parser, $title= '')
Returns the sources of any cascading protection acting on a specified page.
getCustomDefaultSort()
Accessor for $mDefaultSort Unlike getDefaultSort(), will return false if none is set.
Definition: Parser.php:5679
extensionSubstitution($params, $frame)
Return the text to be used for a given extension tag.
Definition: Parser.php:3762
static makeExternalImage($url, $alt= '')
Return the code for images which were added via external links, via Parser::maybeMakeExternalImage()...
Definition: Linker.php:362
recursiveTagParseFully($text, $frame=false)
Fully parse wikitext to fully parsed HTML.
Definition: Parser.php:607
setTransparentTagHook($tag, $callback)
As setHook(), but letting the contents be parsed.
Definition: Parser.php:4689
static element($element, $attribs=[], $contents= '')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:230
wfFindFile($title, $options=[])
Find a file.
Definition: