MediaWiki  master
ParserOutput.php
Go to the documentation of this file.
1 <?php
2 
25 class ParserOutput extends CacheTime {
33 
38 
42  public $mText = null;
43 
49 
53  public $mCategories;
54 
58  public $mIndicators = [];
59 
63  public $mTitleText;
64 
69  public $mLinks = [];
70 
75  public $mTemplates = [];
76 
81  public $mTemplateIds = [];
82 
86  public $mImages = [];
87 
91  public $mFileSearchOptions = [];
92 
96  public $mExternalLinks = [];
97 
102  public $mInterwikiLinks = [];
103 
107  public $mNewSection = false;
108 
112  public $mHideNewSection = false;
113 
117  public $mNoGallery = false;
118 
122  public $mHeadItems = [];
123 
127  public $mModules = [];
128 
132  public $mModuleStyles = [];
133 
137  public $mJsConfigVars = [];
138 
142  public $mOutputHooks = [];
143 
148  public $mWarnings = [];
149 
153  public $mSections = [];
154 
158  public $mProperties = [];
159 
163  public $mTOCHTML = '';
164 
168  public $mTimestamp;
169 
173  public $mEnableOOUI = false;
174 
178  private $mIndexPolicy = '';
179 
183  private $mAccessedOptions = [];
184 
188  private $mExtensionData = [];
189 
193  private $mLimitReportData = [];
194 
196  private $mLimitReportJSData = [];
197 
201  private $mParseStartTime = [];
202 
206  private $mPreventClickjacking = false;
207 
211  private $mFlags = [];
212 
214  private static $speculativeFields = [
215  'speculativePageIdUsed',
216  'mSpeculativeRevId',
217  'revisionTimestampUsed'
218  ];
219 
226 
229 
233  private $mWrapperDivClasses = [];
234 
236  private $mMaxAdaptiveExpiry = INF;
237 
239  '#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)(</(?:mw:)?editsection>))#s';
240 
241  // finalizeAdaptiveCacheExpiry() uses TTL = MAX( m * PARSE_TIME + b, MIN_AR_TTL)
242  // Current values imply that m=3933.333333 and b=-333.333333
243  // See https://www.nngroup.com/articles/website-response-times/
244  const PARSE_FAST_SEC = 0.100; // perceived "fast" page parse
245  const PARSE_SLOW_SEC = 1.0; // perceived "slow" page parse
246  const FAST_AR_TTL = 60; // adaptive TTL for "fast" pages
247  const SLOW_AR_TTL = 3600; // adaptive TTL for "slow" pages
248  const MIN_AR_TTL = 15; // min adaptive TTL (for sanity, pool counter, and edit stashing)
249 
259  public function __construct( $text = '', $languageLinks = [], $categoryLinks = [],
260  $unused = false, $titletext = ''
261  ) {
262  $this->mText = $text;
263  $this->mLanguageLinks = $languageLinks;
264  $this->mCategories = $categoryLinks;
265  $this->mTitleText = $titletext;
266  }
267 
278  public function hasText() {
279  return ( $this->mText !== null );
280  }
281 
290  public function getRawText() {
291  if ( $this->mText === null ) {
292  throw new LogicException( 'This ParserOutput contains no text!' );
293  }
294 
295  return $this->mText;
296  }
297 
323  public function getText( $options = [] ) {
324  $options += [
325  'allowTOC' => true,
326  'enableSectionEditLinks' => true,
327  'unwrap' => false,
328  'deduplicateStyles' => true,
329  'wrapperDivClass' => $this->getWrapperDivClass(),
330  ];
331  $text = $this->getRawText();
332 
333  Hooks::runWithoutAbort( 'ParserOutputPostCacheTransform', [ $this, &$text, &$options ] );
334 
335  if ( $options['wrapperDivClass'] !== '' && !$options['unwrap'] ) {
336  $text = Html::rawElement( 'div', [ 'class' => $options['wrapperDivClass'] ], $text );
337  }
338 
339  if ( $options['enableSectionEditLinks'] ) {
340  $text = preg_replace_callback(
341  self::EDITSECTION_REGEX,
342  function ( $m ) {
343  $editsectionPage = Title::newFromText( htmlspecialchars_decode( $m[1] ) );
344  $editsectionSection = htmlspecialchars_decode( $m[2] );
345  $editsectionContent = isset( $m[4] ) ? Sanitizer::decodeCharReferences( $m[3] ) : null;
346 
347  if ( !is_object( $editsectionPage ) ) {
348  throw new MWException( "Bad parser output text." );
349  }
350 
352  return $context->getSkin()->doEditSectionLink(
353  $editsectionPage,
354  $editsectionSection,
355  $editsectionContent,
356  $context->getLanguage()
357  );
358  },
359  $text
360  );
361  } else {
362  $text = preg_replace( self::EDITSECTION_REGEX, '', $text );
363  }
364 
365  if ( $options['allowTOC'] ) {
366  $text = str_replace( [ Parser::TOC_START, Parser::TOC_END ], '', $text );
367  } else {
368  $text = preg_replace(
369  '#' . preg_quote( Parser::TOC_START, '#' ) . '.*?' . preg_quote( Parser::TOC_END, '#' ) . '#s',
370  '',
371  $text
372  );
373  }
374 
375  if ( $options['deduplicateStyles'] ) {
376  $seen = [];
377  $text = preg_replace_callback(
378  '#<style\s+([^>]*data-mw-deduplicate\s*=[^>]*)>.*?</style>#s',
379  function ( $m ) use ( &$seen ) {
380  $attr = Sanitizer::decodeTagAttributes( $m[1] );
381  if ( !isset( $attr['data-mw-deduplicate'] ) ) {
382  return $m[0];
383  }
384 
385  $key = $attr['data-mw-deduplicate'];
386  if ( !isset( $seen[$key] ) ) {
387  $seen[$key] = true;
388  return $m[0];
389  }
390 
391  // We were going to use an empty <style> here, but there
392  // was concern that would be too much overhead for browsers.
393  // So let's hope a <link> with a non-standard rel and href isn't
394  // going to be misinterpreted or mangled by any subsequent processing.
395  return Html::element( 'link', [
396  'rel' => 'mw-deduplicated-inline-style',
397  'href' => "mw-data:" . wfUrlencode( $key ),
398  ] );
399  },
400  $text
401  );
402  }
403 
404  // Hydrate slot section header placeholders generated by RevisionRenderer.
405  $text = preg_replace_callback(
406  '#<mw:slotheader>(.*?)</mw:slotheader>#',
407  function ( $m ) {
408  $role = htmlspecialchars_decode( $m[1] );
409  // TODO: map to message, using the interface language. Set lang="xyz" accordingly.
410  $headerText = $role;
411  return $headerText;
412  },
413  $text
414  );
415  return $text;
416  }
417 
423  public function addWrapperDivClass( $class ) {
424  $this->mWrapperDivClasses[$class] = true;
425  }
426 
431  public function clearWrapperDivClass() {
432  $this->mWrapperDivClasses = [];
433  }
434 
442  public function getWrapperDivClass() {
443  return implode( ' ', array_keys( $this->mWrapperDivClasses ) );
444  }
445 
450  public function setSpeculativeRevIdUsed( $id ) {
451  $this->mSpeculativeRevId = $id;
452  }
453 
458  public function getSpeculativeRevIdUsed() {
460  }
461 
466  public function setSpeculativePageIdUsed( $id ) {
467  $this->speculativePageIdUsed = $id;
468  }
469 
474  public function getSpeculativePageIdUsed() {
476  }
477 
482  public function setRevisionTimestampUsed( $timestamp ) {
483  $this->revisionTimestampUsed = $timestamp;
484  }
485 
490  public function getRevisionTimestampUsed() {
492  }
493 
498  public function setRevisionUsedSha1Base36( $hash ) {
499  if ( $hash === null ) {
500  return; // e.g. RevisionRecord::getSha1() returned null
501  }
502 
503  if (
504  $this->revisionUsedSha1Base36 !== null &&
505  $this->revisionUsedSha1Base36 !== $hash
506  ) {
507  $this->revisionUsedSha1Base36 = ''; // mismatched
508  } else {
509  $this->revisionUsedSha1Base36 = $hash;
510  }
511  }
512 
517  public function getRevisionUsedSha1Base36() {
519  }
520 
521  public function &getLanguageLinks() {
522  return $this->mLanguageLinks;
523  }
524 
525  public function getInterwikiLinks() {
526  return $this->mInterwikiLinks;
527  }
528 
529  public function getCategoryLinks() {
530  return array_keys( $this->mCategories );
531  }
532 
533  public function &getCategories() {
534  return $this->mCategories;
535  }
536 
541  public function getIndicators() {
542  return $this->mIndicators;
543  }
544 
545  public function getTitleText() {
546  return $this->mTitleText;
547  }
548 
549  public function getSections() {
550  return $this->mSections;
551  }
552 
553  public function &getLinks() {
554  return $this->mLinks;
555  }
556 
557  public function &getTemplates() {
558  return $this->mTemplates;
559  }
560 
561  public function &getTemplateIds() {
562  return $this->mTemplateIds;
563  }
564 
565  public function &getImages() {
566  return $this->mImages;
567  }
568 
569  public function &getFileSearchOptions() {
571  }
572 
573  public function &getExternalLinks() {
574  return $this->mExternalLinks;
575  }
576 
577  public function setNoGallery( $value ) {
578  $this->mNoGallery = (bool)$value;
579  }
580 
581  public function getNoGallery() {
582  return $this->mNoGallery;
583  }
584 
585  public function getHeadItems() {
586  return $this->mHeadItems;
587  }
588 
589  public function getModules() {
590  return $this->mModules;
591  }
592 
593  public function getModuleStyles() {
594  return $this->mModuleStyles;
595  }
596 
601  public function getJsConfigVars() {
602  return $this->mJsConfigVars;
603  }
604 
605  public function getOutputHooks() {
606  return (array)$this->mOutputHooks;
607  }
608 
609  public function getWarnings() {
610  return array_keys( $this->mWarnings );
611  }
612 
613  public function getIndexPolicy() {
614  return $this->mIndexPolicy;
615  }
616 
617  public function getTOCHTML() {
618  return $this->mTOCHTML;
619  }
620 
624  public function getTimestamp() {
625  return $this->mTimestamp;
626  }
627 
628  public function getLimitReportData() {
630  }
631 
632  public function getLimitReportJSData() {
634  }
635 
636  public function getEnableOOUI() {
637  return $this->mEnableOOUI;
638  }
639 
640  public function setText( $text ) {
641  return wfSetVar( $this->mText, $text );
642  }
643 
644  public function setLanguageLinks( $ll ) {
645  return wfSetVar( $this->mLanguageLinks, $ll );
646  }
647 
648  public function setCategoryLinks( $cl ) {
649  return wfSetVar( $this->mCategories, $cl );
650  }
651 
652  public function setTitleText( $t ) {
653  return wfSetVar( $this->mTitleText, $t );
654  }
655 
656  public function setSections( $toc ) {
657  return wfSetVar( $this->mSections, $toc );
658  }
659 
660  public function setIndexPolicy( $policy ) {
661  return wfSetVar( $this->mIndexPolicy, $policy );
662  }
663 
664  public function setTOCHTML( $tochtml ) {
665  return wfSetVar( $this->mTOCHTML, $tochtml );
666  }
667 
668  public function setTimestamp( $timestamp ) {
669  return wfSetVar( $this->mTimestamp, $timestamp );
670  }
671 
672  public function addCategory( $c, $sort ) {
673  $this->mCategories[$c] = $sort;
674  }
675 
681  public function setIndicator( $id, $content ) {
682  $this->mIndicators[$id] = $content;
683  }
684 
692  public function setEnableOOUI( $enable = false ) {
693  $this->mEnableOOUI = $enable;
694  }
695 
696  public function addLanguageLink( $t ) {
697  $this->mLanguageLinks[] = $t;
698  }
699 
700  public function addWarning( $s ) {
701  $this->mWarnings[$s] = 1;
702  }
703 
704  public function addOutputHook( $hook, $data = false ) {
705  $this->mOutputHooks[] = [ $hook, $data ];
706  }
707 
708  public function setNewSection( $value ) {
709  $this->mNewSection = (bool)$value;
710  }
711 
712  public function hideNewSection( $value ) {
713  $this->mHideNewSection = (bool)$value;
714  }
715 
716  public function getHideNewSection() {
717  return (bool)$this->mHideNewSection;
718  }
719 
720  public function getNewSection() {
721  return (bool)$this->mNewSection;
722  }
723 
731  public static function isLinkInternal( $internal, $url ) {
732  return (bool)preg_match( '/^' .
733  # If server is proto relative, check also for http/https links
734  ( substr( $internal, 0, 2 ) === '//' ? '(?:https?:)?' : '' ) .
735  preg_quote( $internal, '/' ) .
736  # check for query/path/anchor or end of link in each case
737  '(?:[\?\/\#]|$)/i',
738  $url
739  );
740  }
741 
742  public function addExternalLink( $url ) {
743  # We don't register links pointing to our own server, unless... :-)
745 
746  # Replace unnecessary URL escape codes with the referenced character
747  # This prevents spammers from hiding links from the filters
748  $url = Parser::normalizeLinkUrl( $url );
749 
750  $registerExternalLink = true;
751  if ( !$wgRegisterInternalExternals ) {
752  $registerExternalLink = !self::isLinkInternal( $wgServer, $url );
753  }
754  if ( $registerExternalLink ) {
755  $this->mExternalLinks[$url] = 1;
756  }
757  }
758 
765  public function addLink( Title $title, $id = null ) {
766  if ( $title->isExternal() ) {
767  // Don't record interwikis in pagelinks
768  $this->addInterwikiLink( $title );
769  return;
770  }
771  $ns = $title->getNamespace();
772  $dbk = $title->getDBkey();
773  if ( $ns == NS_MEDIA ) {
774  // Normalize this pseudo-alias if it makes it down here...
775  $ns = NS_FILE;
776  } elseif ( $ns == NS_SPECIAL ) {
777  // We don't record Special: links currently
778  // It might actually be wise to, but we'd need to do some normalization.
779  return;
780  } elseif ( $dbk === '' ) {
781  // Don't record self links - [[#Foo]]
782  return;
783  }
784  if ( !isset( $this->mLinks[$ns] ) ) {
785  $this->mLinks[$ns] = [];
786  }
787  if ( is_null( $id ) ) {
788  $id = $title->getArticleID();
789  }
790  $this->mLinks[$ns][$dbk] = $id;
791  }
792 
799  public function addImage( $name, $timestamp = null, $sha1 = null ) {
800  $this->mImages[$name] = 1;
801  if ( $timestamp !== null && $sha1 !== null ) {
802  $this->mFileSearchOptions[$name] = [ 'time' => $timestamp, 'sha1' => $sha1 ];
803  }
804  }
805 
812  public function addTemplate( $title, $page_id, $rev_id ) {
813  $ns = $title->getNamespace();
814  $dbk = $title->getDBkey();
815  if ( !isset( $this->mTemplates[$ns] ) ) {
816  $this->mTemplates[$ns] = [];
817  }
818  $this->mTemplates[$ns][$dbk] = $page_id;
819  if ( !isset( $this->mTemplateIds[$ns] ) ) {
820  $this->mTemplateIds[$ns] = [];
821  }
822  $this->mTemplateIds[$ns][$dbk] = $rev_id; // For versioning
823  }
824 
829  public function addInterwikiLink( $title ) {
830  if ( !$title->isExternal() ) {
831  throw new MWException( 'Non-interwiki link passed, internal parser error.' );
832  }
833  $prefix = $title->getInterwiki();
834  if ( !isset( $this->mInterwikiLinks[$prefix] ) ) {
835  $this->mInterwikiLinks[$prefix] = [];
836  }
837  $this->mInterwikiLinks[$prefix][$title->getDBkey()] = 1;
838  }
839 
847  public function addHeadItem( $section, $tag = false ) {
848  if ( $tag !== false ) {
849  $this->mHeadItems[$tag] = $section;
850  } else {
851  $this->mHeadItems[] = $section;
852  }
853  }
854 
858  public function addModules( $modules ) {
859  $this->mModules = array_merge( $this->mModules, (array)$modules );
860  }
861 
865  public function addModuleStyles( $modules ) {
866  $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
867  }
868 
876  public function addJsConfigVars( $keys, $value = null ) {
877  if ( is_array( $keys ) ) {
878  foreach ( $keys as $key => $value ) {
879  $this->mJsConfigVars[$key] = $value;
880  }
881  return;
882  }
883 
884  $this->mJsConfigVars[$keys] = $value;
885  }
886 
892  public function addOutputPageMetadata( OutputPage $out ) {
893  $this->addModules( $out->getModules() );
894  $this->addModuleStyles( $out->getModuleStyles() );
895  $this->addJsConfigVars( $out->getJsConfigVars() );
896 
897  $this->mHeadItems = array_merge( $this->mHeadItems, $out->getHeadItemsArray() );
898  $this->mPreventClickjacking = $this->mPreventClickjacking || $out->getPreventClickjacking();
899  }
900 
917  public function addTrackingCategory( $msg, $title ) {
918  if ( $title->isSpecialPage() ) {
919  wfDebug( __METHOD__ . ": Not adding tracking category $msg to special page!\n" );
920  return false;
921  }
922 
923  // Important to parse with correct title (T33469)
924  $cat = wfMessage( $msg )
925  ->title( $title )
926  ->inContentLanguage()
927  ->text();
928 
929  # Allow tracking categories to be disabled by setting them to "-"
930  if ( $cat === '-' ) {
931  return false;
932  }
933 
934  $containerCategory = Title::makeTitleSafe( NS_CATEGORY, $cat );
935  if ( $containerCategory ) {
936  $this->addCategory( $containerCategory->getDBkey(), $this->getProperty( 'defaultsort' ) ?: '' );
937  return true;
938  } else {
939  wfDebug( __METHOD__ . ": [[MediaWiki:$msg]] is not a valid title!\n" );
940  return false;
941  }
942  }
943 
955  public function setDisplayTitle( $text ) {
956  $this->setTitleText( $text );
957  $this->setProperty( 'displaytitle', $text );
958  }
959 
968  public function getDisplayTitle() {
969  $t = $this->getTitleText();
970  if ( $t === '' ) {
971  return false;
972  }
973  return $t;
974  }
975 
981  public function setFlag( $flag ) {
982  $this->mFlags[$flag] = true;
983  }
984 
989  public function getFlag( $flag ) {
990  return isset( $this->mFlags[$flag] );
991  }
992 
997  public function getAllFlags() {
998  return array_keys( $this->mFlags );
999  }
1000 
1061  public function setProperty( $name, $value ) {
1062  $this->mProperties[$name] = $value;
1063  }
1064 
1073  public function getProperty( $name ) {
1074  return $this->mProperties[$name] ?? false;
1075  }
1076 
1077  public function unsetProperty( $name ) {
1078  unset( $this->mProperties[$name] );
1079  }
1080 
1081  public function getProperties() {
1082  if ( !isset( $this->mProperties ) ) {
1083  $this->mProperties = [];
1084  }
1085  return $this->mProperties;
1086  }
1087 
1093  public function getUsedOptions() {
1094  if ( !isset( $this->mAccessedOptions ) ) {
1095  return [];
1096  }
1097  return array_keys( $this->mAccessedOptions );
1098  }
1099 
1112  public function recordOption( $option ) {
1113  $this->mAccessedOptions[$option] = true;
1114  }
1115 
1156  public function setExtensionData( $key, $value ) {
1157  if ( $value === null ) {
1158  unset( $this->mExtensionData[$key] );
1159  } else {
1160  $this->mExtensionData[$key] = $value;
1161  }
1162  }
1163 
1175  public function getExtensionData( $key ) {
1176  return $this->mExtensionData[$key] ?? null;
1177  }
1178 
1179  private static function getTimes( $clock = null ) {
1180  $ret = [];
1181  if ( !$clock || $clock === 'wall' ) {
1182  $ret['wall'] = microtime( true );
1183  }
1184  if ( !$clock || $clock === 'cpu' ) {
1185  $ru = wfGetRusage();
1186  if ( $ru ) {
1187  $ret['cpu'] = $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6;
1188  $ret['cpu'] += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6;
1189  }
1190  }
1191  return $ret;
1192  }
1193 
1198  public function resetParseStartTime() {
1199  $this->mParseStartTime = self::getTimes();
1200  }
1201 
1213  public function getTimeSinceStart( $clock ) {
1214  if ( !isset( $this->mParseStartTime[$clock] ) ) {
1215  return null;
1216  }
1217 
1218  $end = self::getTimes( $clock );
1219  return $end[$clock] - $this->mParseStartTime[$clock];
1220  }
1221 
1241  public function setLimitReportData( $key, $value ) {
1242  $this->mLimitReportData[$key] = $value;
1243 
1244  if ( is_array( $value ) ) {
1245  if ( array_keys( $value ) === [ 0, 1 ]
1246  && is_numeric( $value[0] )
1247  && is_numeric( $value[1] )
1248  ) {
1249  $data = [ 'value' => $value[0], 'limit' => $value[1] ];
1250  } else {
1251  $data = $value;
1252  }
1253  } else {
1254  $data = $value;
1255  }
1256 
1257  if ( strpos( $key, '-' ) ) {
1258  list( $ns, $name ) = explode( '-', $key, 2 );
1259  $this->mLimitReportJSData[$ns][$name] = $data;
1260  } else {
1261  $this->mLimitReportJSData[$key] = $data;
1262  }
1263  }
1264 
1275  public function hasDynamicContent() {
1276  global $wgParserCacheExpireTime;
1277 
1278  return $this->getCacheExpiry() < $wgParserCacheExpireTime;
1279  }
1280 
1288  public function preventClickjacking( $flag = null ) {
1289  return wfSetVar( $this->mPreventClickjacking, $flag );
1290  }
1291 
1298  public function updateRuntimeAdaptiveExpiry( $ttl ) {
1299  $this->mMaxAdaptiveExpiry = min( $ttl, $this->mMaxAdaptiveExpiry );
1300  $this->updateCacheExpiry( $ttl );
1301  }
1302 
1308  public function finalizeAdaptiveCacheExpiry() {
1309  if ( is_infinite( $this->mMaxAdaptiveExpiry ) ) {
1310  return; // not set
1311  }
1312 
1313  $runtime = $this->getTimeSinceStart( 'wall' );
1314  if ( is_float( $runtime ) ) {
1315  $slope = ( self::SLOW_AR_TTL - self::FAST_AR_TTL )
1316  / ( self::PARSE_SLOW_SEC - self::PARSE_FAST_SEC );
1317  // SLOW_AR_TTL = PARSE_SLOW_SEC * $slope + $point
1318  $point = self::SLOW_AR_TTL - self::PARSE_SLOW_SEC * $slope;
1319 
1320  $adaptiveTTL = min(
1321  max( $slope * $runtime + $point, self::MIN_AR_TTL ),
1322  $this->mMaxAdaptiveExpiry
1323  );
1324  $this->updateCacheExpiry( $adaptiveTTL );
1325  }
1326  }
1327 
1328  public function __sleep() {
1329  return array_filter( array_keys( get_object_vars( $this ) ),
1330  function ( $field ) {
1331  if ( $field === 'mParseStartTime' ) {
1332  return false;
1333  } elseif ( strpos( $field, "\0" ) !== false ) {
1334  // Unserializing unknown private fields in HHVM causes
1335  // member variables with nulls in their names (T229366)
1336  return false;
1337  } else {
1338  return true;
1339  }
1340  }
1341  );
1342  }
1343 
1352  $this->mOutputHooks = self::mergeList( $this->mOutputHooks, $source->getOutputHooks() );
1353  $this->mWarnings = self::mergeMap( $this->mWarnings, $source->mWarnings ); // don't use getter
1354  $this->mTimestamp = $this->useMaxValue( $this->mTimestamp, $source->getTimestamp() );
1355 
1356  foreach ( self::$speculativeFields as $field ) {
1357  if ( $this->$field && $source->$field && $this->$field !== $source->$field ) {
1358  wfLogWarning( __METHOD__ . ": inconsistent '$field' properties!" );
1359  }
1360  $this->$field = $this->useMaxValue( $this->$field, $source->$field );
1361  }
1362 
1363  $this->mParseStartTime = $this->useEachMinValue(
1364  $this->mParseStartTime,
1365  $source->mParseStartTime
1366  );
1367 
1368  $this->mFlags = self::mergeMap( $this->mFlags, $source->mFlags );
1369  $this->mAccessedOptions = self::mergeMap( $this->mAccessedOptions, $source->mAccessedOptions );
1370 
1371  // TODO: maintain per-slot limit reports!
1372  if ( empty( $this->mLimitReportData ) ) {
1373  $this->mLimitReportData = $source->mLimitReportData;
1374  }
1375  if ( empty( $this->mLimitReportJSData ) ) {
1376  $this->mLimitReportJSData = $source->mLimitReportJSData;
1377  }
1378  }
1379 
1388  // HTML and HTTP
1389  $this->mHeadItems = self::mergeMixedList( $this->mHeadItems, $source->getHeadItems() );
1390  $this->mModules = self::mergeList( $this->mModules, $source->getModules() );
1391  $this->mModuleStyles = self::mergeList( $this->mModuleStyles, $source->getModuleStyles() );
1392  $this->mJsConfigVars = self::mergeMap( $this->mJsConfigVars, $source->getJsConfigVars() );
1393  $this->mMaxAdaptiveExpiry = min( $this->mMaxAdaptiveExpiry, $source->mMaxAdaptiveExpiry );
1394 
1395  // "noindex" always wins!
1396  if ( $this->mIndexPolicy === 'noindex' || $source->mIndexPolicy === 'noindex' ) {
1397  $this->mIndexPolicy = 'noindex';
1398  } elseif ( $this->mIndexPolicy !== 'index' ) {
1399  $this->mIndexPolicy = $source->mIndexPolicy;
1400  }
1401 
1402  // Skin control
1403  $this->mNewSection = $this->mNewSection || $source->getNewSection();
1404  $this->mHideNewSection = $this->mHideNewSection || $source->getHideNewSection();
1405  $this->mNoGallery = $this->mNoGallery || $source->getNoGallery();
1406  $this->mEnableOOUI = $this->mEnableOOUI || $source->getEnableOOUI();
1407  $this->mPreventClickjacking = $this->mPreventClickjacking || $source->preventClickjacking();
1408 
1409  // TODO: we'll have to be smarter about this!
1410  $this->mSections = array_merge( $this->mSections, $source->getSections() );
1411  $this->mTOCHTML .= $source->mTOCHTML;
1412 
1413  // XXX: we don't want to concatenate title text, so first write wins.
1414  // We should use the first *modified* title text, but we don't have the original to check.
1415  if ( $this->mTitleText === null || $this->mTitleText === '' ) {
1416  $this->mTitleText = $source->mTitleText;
1417  }
1418 
1419  // class names are stored in array keys
1420  $this->mWrapperDivClasses = self::mergeMap(
1421  $this->mWrapperDivClasses,
1422  $source->mWrapperDivClasses
1423  );
1424 
1425  // NOTE: last write wins, same as within one ParserOutput
1426  $this->mIndicators = self::mergeMap( $this->mIndicators, $source->getIndicators() );
1427 
1428  // NOTE: include extension data in "tracking meta data" as well as "html meta data"!
1429  // TODO: add a $mergeStrategy parameter to setExtensionData to allow different
1430  // kinds of extension data to be merged in different ways.
1431  $this->mExtensionData = self::mergeMap(
1432  $this->mExtensionData,
1433  $source->mExtensionData
1434  );
1435  }
1436 
1445  $this->mLanguageLinks = self::mergeList( $this->mLanguageLinks, $source->getLanguageLinks() );
1446  $this->mCategories = self::mergeMap( $this->mCategories, $source->getCategories() );
1447  $this->mLinks = self::merge2D( $this->mLinks, $source->getLinks() );
1448  $this->mTemplates = self::merge2D( $this->mTemplates, $source->getTemplates() );
1449  $this->mTemplateIds = self::merge2D( $this->mTemplateIds, $source->getTemplateIds() );
1450  $this->mImages = self::mergeMap( $this->mImages, $source->getImages() );
1451  $this->mFileSearchOptions = self::mergeMap(
1452  $this->mFileSearchOptions,
1453  $source->getFileSearchOptions()
1454  );
1455  $this->mExternalLinks = self::mergeMap( $this->mExternalLinks, $source->getExternalLinks() );
1456  $this->mInterwikiLinks = self::merge2D(
1457  $this->mInterwikiLinks,
1458  $source->getInterwikiLinks()
1459  );
1460 
1461  // TODO: add a $mergeStrategy parameter to setProperty to allow different
1462  // kinds of properties to be merged in different ways.
1463  $this->mProperties = self::mergeMap( $this->mProperties, $source->getProperties() );
1464 
1465  // NOTE: include extension data in "tracking meta data" as well as "html meta data"!
1466  // TODO: add a $mergeStrategy parameter to setExtensionData to allow different
1467  // kinds of extension data to be merged in different ways.
1468  $this->mExtensionData = self::mergeMap(
1469  $this->mExtensionData,
1470  $source->mExtensionData
1471  );
1472  }
1473 
1474  private static function mergeMixedList( array $a, array $b ) {
1475  return array_unique( array_merge( $a, $b ), SORT_REGULAR );
1476  }
1477 
1478  private static function mergeList( array $a, array $b ) {
1479  return array_values( array_unique( array_merge( $a, $b ), SORT_REGULAR ) );
1480  }
1481 
1482  private static function mergeMap( array $a, array $b ) {
1483  return array_replace( $a, $b );
1484  }
1485 
1486  private static function merge2D( array $a, array $b ) {
1487  $values = [];
1488  $keys = array_merge( array_keys( $a ), array_keys( $b ) );
1489 
1490  foreach ( $keys as $k ) {
1491  if ( empty( $a[$k] ) ) {
1492  $values[$k] = $b[$k];
1493  } elseif ( empty( $b[$k] ) ) {
1494  $values[$k] = $a[$k];
1495  } elseif ( is_array( $a[$k] ) && is_array( $b[$k] ) ) {
1496  $values[$k] = array_replace( $a[$k], $b[$k] );
1497  } else {
1498  $values[$k] = $b[$k];
1499  }
1500  }
1501 
1502  return $values;
1503  }
1504 
1505  private static function useEachMinValue( array $a, array $b ) {
1506  $values = [];
1507  $keys = array_merge( array_keys( $a ), array_keys( $b ) );
1508 
1509  foreach ( $keys as $k ) {
1510  if ( is_array( $a[$k] ?? null ) && is_array( $b[$k] ?? null ) ) {
1511  $values[$k] = self::useEachMinValue( $a[$k], $b[$k] );
1512  } else {
1513  $values[$k] = self::useMinValue( $a[$k] ?? null, $b[$k] ?? null );
1514  }
1515  }
1516 
1517  return $values;
1518  }
1519 
1520  private static function useMinValue( $a, $b ) {
1521  if ( $a === null ) {
1522  return $b;
1523  }
1524 
1525  if ( $b === null ) {
1526  return $a;
1527  }
1528 
1529  return min( $a, $b );
1530  }
1531 
1532  private static function useMaxValue( $a, $b ) {
1533  if ( $a === null ) {
1534  return $b;
1535  }
1536 
1537  if ( $b === null ) {
1538  return $a;
1539  }
1540 
1541  return max( $a, $b );
1542  }
1543 
1544 }
static useEachMinValue(array $a, array $b)
getPreventClickjacking()
Get the prevent-clickjacking flag.
addCategory( $c, $sort)
static useMaxValue( $a, $b)
return true to allow those checks to and false if checking is done remove or add to the links of a group of changes in EnhancedChangesList Hook subscribers can return false to omit this line from recentchanges use this to change the tables headers change it to an object instance and return false override the list derivative used $groups Array of ChangesListFilterGroup objects(added in 1.34) 'FileDeleteComplete' null for the local wiki Added in
Definition: hooks.txt:1529
getModules( $filter=false, $position=null, $param='mModules', $type=ResourceLoaderModule::TYPE_COMBINED)
Get the list of modules to include on this page.
Definition: OutputPage.php:528
setProperty( $name, $value)
Set a property to be stored in the page_props database table.
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
addTrackingCategory( $msg, $title)
Add a tracking category, getting the title from a system message, or print a debug message if the tit...
getHeadItemsArray()
Get an array of head items.
Definition: OutputPage.php:624
$wgParserCacheExpireTime
The expiry time for the parser cache, in seconds.
getArticleID( $flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition: Title.php:3027
setTOCHTML( $tochtml)
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:231
int null $speculativePageIdUsed
Assumed page ID for {{PAGEID}} if no revision is set.
int null $mSpeculativeRevId
Assumed rev ID for {{REVISIONID}} if no revision is set.
hideNewSection( $value)
setRevisionTimestampUsed( $timestamp)
addModuleStyles( $modules)
const PARSE_FAST_SEC
setText( $text)
__construct( $text='', $languageLinks=[], $categoryLinks=[], $unused=false, $titletext='')
const EDITSECTION_REGEX
setSpeculativePageIdUsed( $id)
getRevisionUsedSha1Base36()
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:1972
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
preventClickjacking( $flag=null)
Get or set the prevent-clickjacking flag.
wfGetRusage()
Get system resource usage of current request context.
in this case you re responsible for computing and outputting the entire conflict i the difference between revisions and your text headers and sections and Diff initially an empty< div id="toolbar"></div > Hook subscribers can return false to have no toolbar HTML be loaded overridable Default is either copyrightwarning or copyrightwarning2 overridable Default is editpage tos summary such as anonymity and the real check
Definition: hooks.txt:1401
setCategoryLinks( $cl)
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
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
$wgRegisterInternalExternals
By default MediaWiki does not register links pointing to same server in externallinks dataset...
const TOC_START
Definition: Parser.php:141
& getFileSearchOptions()
getWrapperDivClass()
Returns the class (or classes) to be used with the wrapper div for this otuput.
$sort
getProperty( $name)
$source
$value
const NS_SPECIAL
Definition: Defines.php:49
static isLinkInternal( $internal, $url)
Checks, if a url is pointing to the own server.
setSpeculativeRevIdUsed( $id)
Parser cache specific expiry check.
Definition: CacheTime.php:29
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
static string [] $speculativeFields
setDisplayTitle( $text)
Override the title to be used for display.
addImage( $name, $timestamp=null, $sha1=null)
Register a file dependency for this output.
addExternalLink( $url)
static getTimes( $clock=null)
addWrapperDivClass( $class)
Add a CSS class to use for the wrapping div.
addModules( $modules)
Apache License January http
getSpeculativePageIdUsed()
This document provides an overview of the usage of PageUpdater and that is
Definition: pageupdater.txt:3
getJsConfigVars()
Get the javascript config vars to include on this page.
static useMinValue( $a, $b)
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password 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:767
setEnableOOUI( $enable=false)
Enables OOUI, if true, in any OutputPage instance this ParserOutput object is added to...
This document describes the XML format used to represent information about external sites known to a MediaWiki installation This information about external sites is used to allow inter wiki links
in the order they appear.
Definition: sitelist.txt:3
addOutputHook( $hook, $data=false)
getDisplayTitle()
Get the title to be used for display.
hasText()
Returns true if text was passed to the constructor, or set using setText().
$modules
setExtensionData( $key, $value)
Attaches arbitrary data to this ParserObject.
getCacheExpiry()
Returns the number of seconds after which this object should expire.
Definition: CacheTime.php:129
isExternal()
Is this Title interwiki?
Definition: Title.php:920
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness, which urlencode encodes by default.
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
int null $revisionTimestampUsed
Assumed rev timestamp for {{REVISIONTIMESTAMP}} if no revision is set.
static getMain()
Get the RequestContext object associated with the main request.
recordOption( $option)
Tags a parser option for use in the cache key for this parser output.
mergeTrackingMetaDataFrom(ParserOutput $source)
Merges dependency tracking metadata such as backlinks, images used, and extension data from $source i...
getExtensionData( $key)
Gets extensions data previously attached to this ParserOutput using setExtensionData().
setIndexPolicy( $policy)
setFlag( $flag)
Attach a flag to the output so that it can be checked later to handle special cases.
getDBkey()
Get the main part with underscores.
Definition: Title.php:1021
addInterwikiLink( $title)
const NS_MEDIA
Definition: Defines.php:48
static mergeList(array $a, array $b)
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
getTimeSinceStart( $clock)
Returns the time since resetParseStartTime() was last called.
const NS_CATEGORY
Definition: Defines.php:74
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1972
scripts txt MediaWiki primary scripts are in the root directory of the software Users should only use these scripts to access the wiki There are also some php that aren t primary scripts but helper files and won t work if they are accessed directly by the web Primary see https
Definition: scripts.txt:21
addOutputPageMetadata(OutputPage $out)
Copy items from the OutputPage object into this one.
static mergeMap(array $a, array $b)
setNoGallery( $value)
static mergeMixedList(array $a, array $b)
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:767
static runWithoutAbort( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:231
static decodeTagAttributes( $text)
Return an associative array of attribute names and values from a partial tag string.
Definition: Sanitizer.php:1450
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:912
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:1045
const NS_FILE
Definition: Defines.php:66
getModuleStyles( $filter=false, $position=null)
Get the list of style-only modules to load on this page.
Definition: OutputPage.php:553
getRawText()
Get the cacheable text with <mw:editsection> markers still in it.
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
resetParseStartTime()
Resets the parse start timestamps for future calls to getTimeSinceStart()
usually copyright or history_copyright This message must be in HTML not wikitext if the section is included from a template $section
Definition: hooks.txt:3039
const SUPPORTS_UNWRAP_TRANSFORM
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:620
static merge2D(array $a, array $b)
addLanguageLink( $t)
mergeInternalMetaDataFrom(ParserOutput $source)
Merges internal metadata such as flags, accessed options, and profiling info from $source into this P...
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...
setLimitReportData( $key, $value)
Sets parser limit report data for a key.
addTemplate( $title, $page_id, $rev_id)
Register a template dependency for this output.
array $mLimitReportJSData
Parser limit report data for JSON.
hasDynamicContent()
Check whether the cache TTL was lowered due to dynamic content.
const SUPPORTS_STATELESS_TRANSFORMS
Feature flags to indicate to extensions that MediaWiki core supports and uses getText() stateless tra...
addHeadItem( $section, $tag=false)
Add some text to the "<head>".
getText( $options=[])
Get the output HTML.
const TOC_END
Definition: Parser.php:142
finalizeAdaptiveCacheExpiry()
Call this when parsing is done to lower the TTL based on low parse times.
unsetProperty( $name)
setLanguageLinks( $ll)
setIndicator( $id, $content)
static decodeCharReferences( $text)
Decode any character references, numeric or named entities, in the text and return a UTF-8 string...
Definition: Sanitizer.php:1669
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:3039
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition: hooks.txt:2621
clearWrapperDivClass()
Clears the CSS class to use for the wrapping div, effectively disabling the wrapper div until addWrap...
addLink(Title $title, $id=null)
Record a local or interwiki inline link for saving in future link tables.
int $mMaxAdaptiveExpiry
Upper bound of expiry based on parse duration.
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 and they certainly aren t ideal for someone who s installing MediaWiki as MediaWiki does not conform to normal Unix filesystem layout Hopefully we ll offer direct support for standard layouts in the but for now *any change to the location of files is unsupported *Moving things and leaving symlinks will *probably *not break but it is *strongly *advised not to try any more intrusive changes to get MediaWiki to conform more closely to your filesystem hierarchy Any such attempt will almost certainly result in unnecessary bugs The standard recommended location to install relative to the web is it should be possible to enable the appropriate rewrite rules by if you can reconfigure the web server
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
$wgServer
URL of the server.
$mWrapperDivClasses
string CSS classes to use for the wrapping div, stored in the array keys.
const PARSE_SLOW_SEC
static normalizeLinkUrl( $url)
Replace unusual escape codes in a URL with their equivalent characters.
Definition: Parser.php:2126
string null $revisionUsedSha1Base36
SHA-1 base 36 hash of any self-transclusion.
setSections( $toc)
setRevisionUsedSha1Base36( $hash)
getFlag( $flag)
getRevisionTimestampUsed()
setTimestamp( $timestamp)
updateCacheExpiry( $seconds)
Sets the number of seconds after which this object should expire.
Definition: CacheTime.php:112
$content
Definition: pageupdater.txt:72
updateRuntimeAdaptiveExpiry( $ttl)
Lower the runtime adaptive TTL to at most this value.
For a write query
Definition: database.txt:26
setNewSection( $value)
mergeHtmlMetaDataFrom(ParserOutput $source)
Merges HTML metadata such as head items, JS config vars, and HTTP cache control info from $source int...
getUsedOptions()
Returns the options from its ParserOptions which have been taken into account to produce this output...
addJsConfigVars( $keys, $value=null)
Add one or more variables to be set in mw.config in JavaScript.
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:322