MediaWiki  1.29.1
ParserTestRunner.php
Go to the documentation of this file.
1 <?php
30 use Wikimedia\ScopedCallback;
31 
35 class ParserTestRunner {
39  private $useTemporaryTables = true;
40 
44  private $setupDone = [
45  'staticSetup' => false,
46  'perTestSetup' => false,
47  'setupDatabase' => false,
48  'setDatabase' => false,
49  'setupUploads' => false,
50  ];
51 
56  private $db;
57 
62  private $dbClone;
63 
67  private $tidySupport;
68 
72  private $tidyDriver = null;
73 
77  private $recorder;
78 
84  private $uploadDir = null;
85 
90  private $fileBackendName;
91 
96  private $regex;
97 
103  private $normalizationFunctions = [];
104 
109  public function __construct( TestRecorder $recorder, $options = [] ) {
110  $this->recorder = $recorder;
111 
112  if ( isset( $options['norm'] ) ) {
113  foreach ( $options['norm'] as $func ) {
114  if ( in_array( $func, [ 'removeTbody', 'trimWhitespace' ] ) ) {
115  $this->normalizationFunctions[] = $func;
116  } else {
117  $this->recorder->warning(
118  "Warning: unknown normalization option \"$func\"\n" );
119  }
120  }
121  }
122 
123  if ( isset( $options['regex'] ) && $options['regex'] !== false ) {
124  $this->regex = $options['regex'];
125  } else {
126  # Matches anything
127  $this->regex = '//';
128  }
129 
130  $this->keepUploads = !empty( $options['keep-uploads'] );
131 
132  $this->fileBackendName = isset( $options['file-backend'] ) ?
133  $options['file-backend'] : false;
134 
135  $this->runDisabled = !empty( $options['run-disabled'] );
136  $this->runParsoid = !empty( $options['run-parsoid'] );
137 
138  $this->tidySupport = new TidySupport( !empty( $options['use-tidy-config'] ) );
139  if ( !$this->tidySupport->isEnabled() ) {
140  $this->recorder->warning(
141  "Warning: tidy is not installed, skipping some tests\n" );
142  }
143 
144  if ( isset( $options['upload-dir'] ) ) {
145  $this->uploadDir = $options['upload-dir'];
146  }
147  }
148 
149  public function getRecorder() {
150  return $this->recorder;
151  }
152 
172  public function staticSetup( $nextTeardown = null ) {
173  // A note on coding style:
174 
175  // The general idea here is to keep setup code together with
176  // corresponding teardown code, in a fine-grained manner. We have two
177  // arrays: $setup and $teardown. The code snippets in the $setup array
178  // are executed at the end of the method, before it returns, and the
179  // code snippets in the $teardown array are executed in reverse order
180  // when the Wikimedia\ScopedCallback object is consumed.
181 
182  // Because it is a common operation to save, set and restore global
183  // variables, we have an additional convention: when the array key of
184  // $setup is a string, the string is taken to be the name of the global
185  // variable, and the element value is taken to be the desired new value.
186 
187  // It's acceptable to just do the setup immediately, instead of adding
188  // a closure to $setup, except when the setup action depends on global
189  // variable initialisation being done first. In this case, you have to
190  // append a closure to $setup after the global variable is appended.
191 
192  // When you add to setup functions in this class, please keep associated
193  // setup and teardown actions together in the source code, and please
194  // add comments explaining why the setup action is necessary.
195 
196  $setup = [];
197  $teardown = [];
198 
199  $teardown[] = $this->markSetupDone( 'staticSetup' );
200 
201  // Some settings which influence HTML output
202  $setup['wgSitename'] = 'MediaWiki';
203  $setup['wgServer'] = 'http://example.org';
204  $setup['wgServerName'] = 'example.org';
205  $setup['wgScriptPath'] = '';
206  $setup['wgScript'] = '/index.php';
207  $setup['wgResourceBasePath'] = '';
208  $setup['wgStylePath'] = '/skins';
209  $setup['wgExtensionAssetsPath'] = '/extensions';
210  $setup['wgArticlePath'] = '/wiki/$1';
211  $setup['wgActionPaths'] = [];
212  $setup['wgVariantArticlePath'] = false;
213  $setup['wgUploadNavigationUrl'] = false;
214  $setup['wgCapitalLinks'] = true;
215  $setup['wgNoFollowLinks'] = true;
216  $setup['wgNoFollowDomainExceptions'] = [ 'no-nofollow.org' ];
217  $setup['wgExternalLinkTarget'] = false;
218  $setup['wgExperimentalHtmlIds'] = false;
219  $setup['wgLocaltimezone'] = 'UTC';
220  $setup['wgHtml5'] = true;
221  $setup['wgDisableLangConversion'] = false;
222  $setup['wgDisableTitleConversion'] = false;
223 
224  // "extra language links"
225  // see https://gerrit.wikimedia.org/r/111390
226  $setup['wgExtraInterlanguageLinkPrefixes'] = [ 'mul' ];
227 
228  // All FileRepo changes should be done here by injecting services,
229  // there should be no need to change global variables.
230  RepoGroup::setSingleton( $this->createRepoGroup() );
231  $teardown[] = function () {
233  };
234 
235  // Set up null lock managers
236  $setup['wgLockManagers'] = [ [
237  'name' => 'fsLockManager',
238  'class' => 'NullLockManager',
239  ], [
240  'name' => 'nullLockManager',
241  'class' => 'NullLockManager',
242  ] ];
243  $reset = function() {
245  };
246  $setup[] = $reset;
247  $teardown[] = $reset;
248 
249  // This allows article insertion into the prefixed DB
250  $setup['wgDefaultExternalStore'] = false;
251 
252  // This might slightly reduce memory usage
253  $setup['wgAdaptiveMessageCache'] = true;
254 
255  // This is essential and overrides disabling of database messages in TestSetup
256  $setup['wgUseDatabaseMessages'] = true;
257  $reset = function () {
259  };
260  $setup[] = $reset;
261  $teardown[] = $reset;
262 
263  // It's not necessary to actually convert any files
264  $setup['wgSVGConverter'] = 'null';
265  $setup['wgSVGConverters'] = [ 'null' => 'echo "1">$output' ];
266 
267  // Fake constant timestamp
268  Hooks::register( 'ParserGetVariableValueTs', 'ParserTestRunner::getFakeTimestamp' );
269  $teardown[] = function () {
270  Hooks::clear( 'ParserGetVariableValueTs' );
271  };
272 
273  $this->appendNamespaceSetup( $setup, $teardown );
274 
275  // Set up interwikis and append teardown function
276  $teardown[] = $this->setupInterwikis();
277 
278  // This affects title normalization in links. It invalidates
279  // MediaWikiTitleCodec objects.
280  $setup['wgLocalInterwikis'] = [ 'local', 'mi' ];
281  $reset = function () {
282  $this->resetTitleServices();
283  };
284  $setup[] = $reset;
285  $teardown[] = $reset;
286 
287  // Set up a mock MediaHandlerFactory
288  MediaWikiServices::getInstance()->disableService( 'MediaHandlerFactory' );
289  MediaWikiServices::getInstance()->redefineService(
290  'MediaHandlerFactory',
291  function() {
292  return new MockMediaHandlerFactory();
293  }
294  );
295  $teardown[] = function () {
296  MediaWikiServices::getInstance()->resetServiceForTesting( 'MediaHandlerFactory' );
297  };
298 
299  // SqlBagOStuff broke when using temporary tables on r40209 (T17892).
300  // It seems to have been fixed since (r55079?), but regressed at some point before r85701.
301  // This works around it for now...
303  $setup['wgObjectCaches'] = [ CACHE_DB => $wgObjectCaches['hash'] ] + $wgObjectCaches;
304  if ( isset( ObjectCache::$instances[CACHE_DB] ) ) {
305  $savedCache = ObjectCache::$instances[CACHE_DB];
307  $teardown[] = function () use ( $savedCache ) {
308  ObjectCache::$instances[CACHE_DB] = $savedCache;
309  };
310  }
311 
312  $teardown[] = $this->executeSetupSnippets( $setup );
313 
314  // Schedule teardown snippets in reverse order
315  return $this->createTeardownObject( $teardown, $nextTeardown );
316  }
317 
318  private function appendNamespaceSetup( &$setup, &$teardown ) {
319  // Add a namespace shadowing a interwiki link, to test
320  // proper precedence when resolving links. (T53680)
321  $setup['wgExtraNamespaces'] = [
322  100 => 'MemoryAlpha',
323  101 => 'MemoryAlpha_talk'
324  ];
325  // Changing wgExtraNamespaces invalidates caches in MWNamespace and
326  // any live Language object, both on setup and teardown
327  $reset = function () {
329  $GLOBALS['wgContLang']->resetNamespaces();
330  };
331  $setup[] = $reset;
332  $teardown[] = $reset;
333  }
334 
339  protected function createRepoGroup() {
340  if ( $this->uploadDir ) {
341  if ( $this->fileBackendName ) {
342  throw new MWException( 'You cannot specify both use-filebackend and upload-dir' );
343  }
344  $backend = new FSFileBackend( [
345  'name' => 'local-backend',
346  'wikiId' => wfWikiID(),
347  'basePath' => $this->uploadDir,
348  'tmpDirectory' => wfTempDir()
349  ] );
350  } elseif ( $this->fileBackendName ) {
352  $name = $this->fileBackendName;
353  $useConfig = false;
354  foreach ( $wgFileBackends as $conf ) {
355  if ( $conf['name'] === $name ) {
356  $useConfig = $conf;
357  }
358  }
359  if ( $useConfig === false ) {
360  throw new MWException( "Unable to find file backend \"$name\"" );
361  }
362  $useConfig['name'] = 'local-backend'; // swap name
363  unset( $useConfig['lockManager'] );
364  unset( $useConfig['fileJournal'] );
365  $class = $useConfig['class'];
366  $backend = new $class( $useConfig );
367  } else {
368  # Replace with a mock. We do not care about generating real
369  # files on the filesystem, just need to expose the file
370  # informations.
371  $backend = new MockFileBackend( [
372  'name' => 'local-backend',
373  'wikiId' => wfWikiID()
374  ] );
375  }
376 
377  return new RepoGroup(
378  [
379  'class' => 'MockLocalRepo',
380  'name' => 'local',
381  'url' => 'http://example.com/images',
382  'hashLevels' => 2,
383  'transformVia404' => false,
384  'backend' => $backend
385  ],
386  []
387  );
388  }
389 
403  protected function executeSetupSnippets( $setup ) {
404  $saved = [];
405  foreach ( $setup as $name => $value ) {
406  if ( is_int( $name ) ) {
407  $value();
408  } else {
409  $saved[$name] = isset( $GLOBALS[$name] ) ? $GLOBALS[$name] : null;
410  $GLOBALS[$name] = $value;
411  }
412  }
413  return function () use ( $saved ) {
414  $this->executeSetupSnippets( $saved );
415  };
416  }
417 
430  protected function createTeardownObject( $teardown, $nextTeardown = null ) {
431  return new ScopedCallback( function() use ( $teardown, $nextTeardown ) {
432  // Schedule teardown snippets in reverse order
433  $teardown = array_reverse( $teardown );
434 
435  $this->executeSetupSnippets( $teardown );
436  if ( $nextTeardown ) {
437  ScopedCallback::consume( $nextTeardown );
438  }
439  } );
440  }
441 
449  protected function markSetupDone( $funcName ) {
450  if ( $this->setupDone[$funcName] ) {
451  throw new MWException( "$funcName is already done" );
452  }
453  $this->setupDone[$funcName] = true;
454  return function () use ( $funcName ) {
455  $this->setupDone[$funcName] = false;
456  };
457  }
458 
463  protected function checkSetupDone( $funcName, $funcName2 = null ) {
464  if ( !$this->setupDone[$funcName]
465  && ( $funcName === null || !$this->setupDone[$funcName2] )
466  ) {
467  throw new MWException( "$funcName must be called before calling " .
468  wfGetCaller() );
469  }
470  }
471 
478  public function isSetupDone( $funcName ) {
479  return isset( $this->setupDone[$funcName] ) ? $this->setupDone[$funcName] : false;
480  }
481 
493  private function setupInterwikis() {
494  # Hack: insert a few Wikipedia in-project interwiki prefixes,
495  # for testing inter-language links
496  Hooks::register( 'InterwikiLoadPrefix', function ( $prefix, &$iwData ) {
497  static $testInterwikis = [
498  'local' => [
499  'iw_url' => 'http://doesnt.matter.org/$1',
500  'iw_api' => '',
501  'iw_wikiid' => '',
502  'iw_local' => 0 ],
503  'wikipedia' => [
504  'iw_url' => 'http://en.wikipedia.org/wiki/$1',
505  'iw_api' => '',
506  'iw_wikiid' => '',
507  'iw_local' => 0 ],
508  'meatball' => [
509  'iw_url' => 'http://www.usemod.com/cgi-bin/mb.pl?$1',
510  'iw_api' => '',
511  'iw_wikiid' => '',
512  'iw_local' => 0 ],
513  'memoryalpha' => [
514  'iw_url' => 'http://www.memory-alpha.org/en/index.php/$1',
515  'iw_api' => '',
516  'iw_wikiid' => '',
517  'iw_local' => 0 ],
518  'zh' => [
519  'iw_url' => 'http://zh.wikipedia.org/wiki/$1',
520  'iw_api' => '',
521  'iw_wikiid' => '',
522  'iw_local' => 1 ],
523  'es' => [
524  'iw_url' => 'http://es.wikipedia.org/wiki/$1',
525  'iw_api' => '',
526  'iw_wikiid' => '',
527  'iw_local' => 1 ],
528  'fr' => [
529  'iw_url' => 'http://fr.wikipedia.org/wiki/$1',
530  'iw_api' => '',
531  'iw_wikiid' => '',
532  'iw_local' => 1 ],
533  'ru' => [
534  'iw_url' => 'http://ru.wikipedia.org/wiki/$1',
535  'iw_api' => '',
536  'iw_wikiid' => '',
537  'iw_local' => 1 ],
538  'mi' => [
539  'iw_url' => 'http://mi.wikipedia.org/wiki/$1',
540  'iw_api' => '',
541  'iw_wikiid' => '',
542  'iw_local' => 1 ],
543  'mul' => [
544  'iw_url' => 'http://wikisource.org/wiki/$1',
545  'iw_api' => '',
546  'iw_wikiid' => '',
547  'iw_local' => 1 ],
548  ];
549  if ( array_key_exists( $prefix, $testInterwikis ) ) {
550  $iwData = $testInterwikis[$prefix];
551  }
552 
553  // We only want to rely on the above fixtures
554  return false;
555  } );// hooks::register
556 
557  return function () {
558  // Tear down
559  Hooks::clear( 'InterwikiLoadPrefix' );
560  };
561  }
562 
567  private function resetTitleServices() {
568  $services = MediaWikiServices::getInstance();
569  $services->resetServiceForTesting( 'TitleFormatter' );
570  $services->resetServiceForTesting( 'TitleParser' );
571  $services->resetServiceForTesting( '_MediaWikiTitleCodec' );
572  $services->resetServiceForTesting( 'LinkRenderer' );
573  $services->resetServiceForTesting( 'LinkRendererFactory' );
574  }
575 
582  public static function chomp( $s ) {
583  if ( substr( $s, -1 ) === "\n" ) {
584  return substr( $s, 0, -1 );
585  } else {
586  return $s;
587  }
588  }
589 
603  public function runTestsFromFiles( $filenames ) {
604  $ok = false;
605 
606  $teardownGuard = $this->staticSetup();
607  $teardownGuard = $this->setupDatabase( $teardownGuard );
608  $teardownGuard = $this->setupUploads( $teardownGuard );
609 
610  $this->recorder->start();
611  try {
612  $ok = true;
613 
614  foreach ( $filenames as $filename ) {
615  $testFileInfo = TestFileReader::read( $filename, [
616  'runDisabled' => $this->runDisabled,
617  'runParsoid' => $this->runParsoid,
618  'regex' => $this->regex ] );
619 
620  // Don't start the suite if there are no enabled tests in the file
621  if ( !$testFileInfo['tests'] ) {
622  continue;
623  }
624 
625  $this->recorder->startSuite( $filename );
626  $ok = $this->runTests( $testFileInfo ) && $ok;
627  $this->recorder->endSuite( $filename );
628  }
629 
630  $this->recorder->report();
631  } catch ( DBError $e ) {
632  $this->recorder->warning( $e->getMessage() );
633  }
634  $this->recorder->end();
635 
636  ScopedCallback::consume( $teardownGuard );
637 
638  return $ok;
639  }
640 
645  public function meetsRequirements( $requirements ) {
646  foreach ( $requirements as $requirement ) {
647  switch ( $requirement['type'] ) {
648  case 'hook':
649  $ok = $this->requireHook( $requirement['name'] );
650  break;
651  case 'functionHook':
652  $ok = $this->requireFunctionHook( $requirement['name'] );
653  break;
654  case 'transparentHook':
655  $ok = $this->requireTransparentHook( $requirement['name'] );
656  break;
657  }
658  if ( !$ok ) {
659  return false;
660  }
661  }
662  return true;
663  }
664 
672  public function runTests( $testFileInfo ) {
673  $ok = true;
674 
675  $this->checkSetupDone( 'staticSetup' );
676 
677  // Don't add articles from the file if there are no enabled tests from the file
678  if ( !$testFileInfo['tests'] ) {
679  return true;
680  }
681 
682  // If any requirements are not met, mark all tests from the file as skipped
683  if ( !$this->meetsRequirements( $testFileInfo['requirements'] ) ) {
684  foreach ( $testFileInfo['tests'] as $test ) {
685  $this->recorder->startTest( $test );
686  $this->recorder->skipped( $test, 'required extension not enabled' );
687  }
688  return true;
689  }
690 
691  // Add articles
692  $this->addArticles( $testFileInfo['articles'] );
693 
694  // Run tests
695  foreach ( $testFileInfo['tests'] as $test ) {
696  $this->recorder->startTest( $test );
697  $result =
698  $this->runTest( $test );
699  if ( $result !== false ) {
700  $ok = $ok && $result->isSuccess();
701  $this->recorder->record( $test, $result );
702  }
703  }
704 
705  return $ok;
706  }
707 
714  function getParser( $preprocessor = null ) {
715  global $wgParserConf;
716 
717  $class = $wgParserConf['class'];
718  $parser = new $class( [ 'preprocessorClass' => $preprocessor ] + $wgParserConf );
720 
721  return $parser;
722  }
723 
741  public function runTest( $test ) {
742  wfDebug( __METHOD__.": running {$test['desc']}" );
743  $opts = $this->parseOptions( $test['options'] );
744  $teardownGuard = $this->perTestSetup( $test );
745 
747  $user = $context->getUser();
749 
750  if ( isset( $opts['tidy'] ) ) {
751  if ( !$this->tidySupport->isEnabled() ) {
752  $this->recorder->skipped( $test, 'tidy extension is not installed' );
753  return false;
754  } else {
755  $options->setTidy( true );
756  }
757  }
758 
759  if ( isset( $opts['title'] ) ) {
760  $titleText = $opts['title'];
761  } else {
762  $titleText = 'Parser test';
763  }
764 
765  $local = isset( $opts['local'] );
766  $preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null;
767  $parser = $this->getParser( $preprocessor );
768  $title = Title::newFromText( $titleText );
769 
770  if ( isset( $opts['pst'] ) ) {
771  $out = $parser->preSaveTransform( $test['input'], $title, $user, $options );
772  } elseif ( isset( $opts['msg'] ) ) {
773  $out = $parser->transformMsg( $test['input'], $options, $title );
774  } elseif ( isset( $opts['section'] ) ) {
775  $section = $opts['section'];
776  $out = $parser->getSection( $test['input'], $section );
777  } elseif ( isset( $opts['replace'] ) ) {
778  $section = $opts['replace'][0];
779  $replace = $opts['replace'][1];
780  $out = $parser->replaceSection( $test['input'], $section, $replace );
781  } elseif ( isset( $opts['comment'] ) ) {
782  $out = Linker::formatComment( $test['input'], $title, $local );
783  } elseif ( isset( $opts['preload'] ) ) {
784  $out = $parser->getPreloadText( $test['input'], $title, $options );
785  } else {
786  $output = $parser->parse( $test['input'], $title, $options, true, true, 1337 );
787  $output->setTOCEnabled( !isset( $opts['notoc'] ) );
788  $out = $output->getText();
789  if ( isset( $opts['tidy'] ) ) {
790  $out = preg_replace( '/\s+$/', '', $out );
791  }
792 
793  if ( isset( $opts['showtitle'] ) ) {
794  if ( $output->getTitleText() ) {
795  $title = $output->getTitleText();
796  }
797 
798  $out = "$title\n$out";
799  }
800 
801  if ( isset( $opts['showindicators'] ) ) {
802  $indicators = '';
803  foreach ( $output->getIndicators() as $id => $content ) {
804  $indicators .= "$id=$content\n";
805  }
806  $out = $indicators . $out;
807  }
808 
809  if ( isset( $opts['ill'] ) ) {
810  $out = implode( ' ', $output->getLanguageLinks() );
811  } elseif ( isset( $opts['cat'] ) ) {
812  $out = '';
813  foreach ( $output->getCategories() as $name => $sortkey ) {
814  if ( $out !== '' ) {
815  $out .= "\n";
816  }
817  $out .= "cat=$name sort=$sortkey";
818  }
819  }
820  }
821 
822  ScopedCallback::consume( $teardownGuard );
823 
824  $expected = $test['result'];
825  if ( count( $this->normalizationFunctions ) ) {
827  $test['expected'], $this->normalizationFunctions );
828  $out = ParserTestResultNormalizer::normalize( $out, $this->normalizationFunctions );
829  }
830 
831  $testResult = new ParserTestResult( $test, $expected, $out );
832  return $testResult;
833  }
834 
842  private static function getOptionValue( $key, $opts, $default ) {
843  $key = strtolower( $key );
844 
845  if ( isset( $opts[$key] ) ) {
846  return $opts[$key];
847  } else {
848  return $default;
849  }
850  }
851 
859  private function parseOptions( $instring ) {
860  $opts = [];
861  // foo
862  // foo=bar
863  // foo="bar baz"
864  // foo=[[bar baz]]
865  // foo=bar,"baz quux"
866  // foo={...json...}
867  $defs = '(?(DEFINE)
868  (?<qstr> # Quoted string
869  "
870  (?:[^\\\\"] | \\\\.)*
871  "
872  )
873  (?<json>
874  \{ # Open bracket
875  (?:
876  [^"{}] | # Not a quoted string or object, or
877  (?&qstr) | # A quoted string, or
878  (?&json) # A json object (recursively)
879  )*
880  \} # Close bracket
881  )
882  (?<value>
883  (?:
884  (?&qstr) # Quoted val
885  |
886  \[\[
887  [^]]* # Link target
888  \]\]
889  |
890  [\w-]+ # Plain word
891  |
892  (?&json) # JSON object
893  )
894  )
895  )';
896  $regex = '/' . $defs . '\b
897  (?<k>[\w-]+) # Key
898  \b
899  (?:\s*
900  = # First sub-value
901  \s*
902  (?<v>
903  (?&value)
904  (?:\s*
905  , # Sub-vals 1..N
906  \s*
907  (?&value)
908  )*
909  )
910  )?
911  /x';
912  $valueregex = '/' . $defs . '(?&value)/x';
913 
914  if ( preg_match_all( $regex, $instring, $matches, PREG_SET_ORDER ) ) {
915  foreach ( $matches as $bits ) {
916  $key = strtolower( $bits['k'] );
917  if ( !isset( $bits['v'] ) ) {
918  $opts[$key] = true;
919  } else {
920  preg_match_all( $valueregex, $bits['v'], $vmatches );
921  $opts[$key] = array_map( [ $this, 'cleanupOption' ], $vmatches[0] );
922  if ( count( $opts[$key] ) == 1 ) {
923  $opts[$key] = $opts[$key][0];
924  }
925  }
926  }
927  }
928  return $opts;
929  }
930 
931  private function cleanupOption( $opt ) {
932  if ( substr( $opt, 0, 1 ) == '"' ) {
933  return stripcslashes( substr( $opt, 1, -1 ) );
934  }
935 
936  if ( substr( $opt, 0, 2 ) == '[[' ) {
937  return substr( $opt, 2, -2 );
938  }
939 
940  if ( substr( $opt, 0, 1 ) == '{' ) {
941  return FormatJson::decode( $opt, true );
942  }
943  return $opt;
944  }
945 
955  public function perTestSetup( $test, $nextTeardown = null ) {
956  $teardown = [];
957 
958  $this->checkSetupDone( 'setupDatabase', 'setDatabase' );
959  $teardown[] = $this->markSetupDone( 'perTestSetup' );
960 
961  $opts = $this->parseOptions( $test['options'] );
962  $config = $test['config'];
963 
964  // Find out values for some special options.
965  $langCode =
966  self::getOptionValue( 'language', $opts, 'en' );
967  $variant =
968  self::getOptionValue( 'variant', $opts, false );
969  $maxtoclevel =
970  self::getOptionValue( 'wgMaxTocLevel', $opts, 999 );
971  $linkHolderBatchSize =
972  self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 );
973 
974  $setup = [
975  'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ),
976  'wgLanguageCode' => $langCode,
977  'wgRawHtml' => self::getOptionValue( 'wgRawHtml', $opts, false ),
978  'wgNamespacesWithSubpages' => array_fill_keys(
979  MWNamespace::getValidNamespaces(), isset( $opts['subpage'] )
980  ),
981  'wgMaxTocLevel' => $maxtoclevel,
982  'wgAllowExternalImages' => self::getOptionValue( 'wgAllowExternalImages', $opts, true ),
983  'wgThumbLimits' => [ self::getOptionValue( 'thumbsize', $opts, 180 ) ],
984  'wgDefaultLanguageVariant' => $variant,
985  'wgLinkHolderBatchSize' => $linkHolderBatchSize,
986  // Set as a JSON object like:
987  // wgEnableMagicLinks={"ISBN":false, "PMID":false, "RFC":false}
988  'wgEnableMagicLinks' => self::getOptionValue( 'wgEnableMagicLinks', $opts, [] )
989  + [ 'ISBN' => true, 'PMID' => true, 'RFC' => true ],
990  ];
991 
992  if ( $config ) {
993  $configLines = explode( "\n", $config );
994 
995  foreach ( $configLines as $line ) {
996  list( $var, $value ) = explode( '=', $line, 2 );
997  $setup[$var] = eval( "return $value;" );
998  }
999  }
1000 
1002  Hooks::run( 'ParserTestGlobals', [ &$setup ] );
1003 
1004  // Create tidy driver
1005  if ( isset( $opts['tidy'] ) ) {
1006  // Cache a driver instance
1007  if ( $this->tidyDriver === null ) {
1008  $this->tidyDriver = MWTidy::factory( $this->tidySupport->getConfig() );
1009  }
1010  $tidy = $this->tidyDriver;
1011  } else {
1012  $tidy = false;
1013  }
1014  MWTidy::setInstance( $tidy );
1015  $teardown[] = function () {
1017  };
1018 
1019  // Set content language. This invalidates the magic word cache and title services
1020  $lang = Language::factory( $langCode );
1021  $setup['wgContLang'] = $lang;
1022  $reset = function () {
1024  $this->resetTitleServices();
1025  };
1026  $setup[] = $reset;
1027  $teardown[] = $reset;
1028 
1029  // Make a user object with the same language
1030  $user = new User;
1031  $user->setOption( 'language', $langCode );
1032  $setup['wgLang'] = $lang;
1033 
1034  // We (re)set $wgThumbLimits to a single-element array above.
1035  $user->setOption( 'thumbsize', 0 );
1036 
1037  $setup['wgUser'] = $user;
1038 
1039  // And put both user and language into the context
1041  $context->setUser( $user );
1042  $context->setLanguage( $lang );
1043  $teardown[] = function () use ( $context ) {
1044  // Reset context to the restored globals
1045  $context->setUser( $GLOBALS['wgUser'] );
1046  $context->setLanguage( $GLOBALS['wgContLang'] );
1047  };
1048 
1049  $teardown[] = $this->executeSetupSnippets( $setup );
1050 
1051  return $this->createTeardownObject( $teardown, $nextTeardown );
1052  }
1053 
1059  private function listTables() {
1060  $tables = [ 'user', 'user_properties', 'user_former_groups', 'page', 'page_restrictions',
1061  'protected_titles', 'revision', 'text', 'pagelinks', 'imagelinks',
1062  'categorylinks', 'templatelinks', 'externallinks', 'langlinks', 'iwlinks',
1063  'site_stats', 'ipblocks', 'image', 'oldimage',
1064  'recentchanges', 'watchlist', 'interwiki', 'logging', 'log_search',
1065  'querycache', 'objectcache', 'job', 'l10n_cache', 'redirect', 'querycachetwo',
1066  'archive', 'user_groups', 'page_props', 'category'
1067  ];
1068 
1069  if ( in_array( $this->db->getType(), [ 'mysql', 'sqlite', 'oracle' ] ) ) {
1070  array_push( $tables, 'searchindex' );
1071  }
1072 
1073  // Allow extensions to add to the list of tables to duplicate;
1074  // may be necessary if they hook into page save or other code
1075  // which will require them while running tests.
1076  Hooks::run( 'ParserTestTables', [ &$tables ] );
1077 
1078  return $tables;
1079  }
1080 
1081  public function setDatabase( IDatabase $db ) {
1082  $this->db = $db;
1083  $this->setupDone['setDatabase'] = true;
1084  }
1085 
1103  public function setupDatabase( $nextTeardown = null ) {
1105 
1106  $this->db = wfGetDB( DB_MASTER );
1107  $dbType = $this->db->getType();
1108 
1109  if ( $dbType == 'oracle' ) {
1110  $suspiciousPrefixes = [ 'pt_', MediaWikiTestCase::ORA_DB_PREFIX ];
1111  } else {
1112  $suspiciousPrefixes = [ 'parsertest_', MediaWikiTestCase::DB_PREFIX ];
1113  }
1114  if ( in_array( $wgDBprefix, $suspiciousPrefixes ) ) {
1115  throw new MWException( "\$wgDBprefix=$wgDBprefix suggests DB setup is already done" );
1116  }
1117 
1118  $teardown = [];
1119 
1120  $teardown[] = $this->markSetupDone( 'setupDatabase' );
1121 
1122  # CREATE TEMPORARY TABLE breaks if there is more than one server
1123  if ( wfGetLB()->getServerCount() != 1 ) {
1124  $this->useTemporaryTables = false;
1125  }
1126 
1127  $temporary = $this->useTemporaryTables || $dbType == 'postgres';
1128  $prefix = $dbType != 'oracle' ? 'parsertest_' : 'pt_';
1129 
1130  $this->dbClone = new CloneDatabase( $this->db, $this->listTables(), $prefix );
1131  $this->dbClone->useTemporaryTables( $temporary );
1132  $this->dbClone->cloneTableStructure();
1133 
1134  if ( $dbType == 'oracle' ) {
1135  $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
1136  # Insert 0 user to prevent FK violations
1137 
1138  # Anonymous user
1139  $this->db->insert( 'user', [
1140  'user_id' => 0,
1141  'user_name' => 'Anonymous' ] );
1142  }
1143 
1144  $teardown[] = function () {
1145  $this->teardownDatabase();
1146  };
1147 
1148  // Wipe some DB query result caches on setup and teardown
1149  $reset = function () {
1150  LinkCache::singleton()->clear();
1151 
1152  // Clear the message cache
1153  MessageCache::singleton()->clear();
1154  };
1155  $reset();
1156  $teardown[] = $reset;
1157  return $this->createTeardownObject( $teardown, $nextTeardown );
1158  }
1159 
1168  public function setupUploads( $nextTeardown = null ) {
1169  $teardown = [];
1170 
1171  $this->checkSetupDone( 'setupDatabase', 'setDatabase' );
1172  $teardown[] = $this->markSetupDone( 'setupUploads' );
1173 
1174  // Create the files in the upload directory (or pretend to create them
1175  // in a MockFileBackend). Append teardown callback.
1176  $teardown[] = $this->setupUploadBackend();
1177 
1178  // Create a user
1179  $user = User::createNew( 'WikiSysop' );
1180 
1181  // Register the uploads in the database
1182 
1183  $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) );
1184  # note that the size/width/height/bits/etc of the file
1185  # are actually set by inspecting the file itself; the arguments
1186  # to recordUpload2 have no effect. That said, we try to make things
1187  # match up so it is less confusing to readers of the code & tests.
1188  $image->recordUpload2( '', 'Upload of some lame file', 'Some lame file', [
1189  'size' => 7881,
1190  'width' => 1941,
1191  'height' => 220,
1192  'bits' => 8,
1193  'media_type' => MEDIATYPE_BITMAP,
1194  'mime' => 'image/jpeg',
1195  'metadata' => serialize( [] ),
1196  'sha1' => Wikimedia\base_convert( '1', 16, 36, 31 ),
1197  'fileExists' => true
1198  ], $this->db->timestamp( '20010115123500' ), $user );
1199 
1200  $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Thumb.png' ) );
1201  # again, note that size/width/height below are ignored; see above.
1202  $image->recordUpload2( '', 'Upload of some lame thumbnail', 'Some lame thumbnail', [
1203  'size' => 22589,
1204  'width' => 135,
1205  'height' => 135,
1206  'bits' => 8,
1207  'media_type' => MEDIATYPE_BITMAP,
1208  'mime' => 'image/png',
1209  'metadata' => serialize( [] ),
1210  'sha1' => Wikimedia\base_convert( '2', 16, 36, 31 ),
1211  'fileExists' => true
1212  ], $this->db->timestamp( '20130225203040' ), $user );
1213 
1214  $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.svg' ) );
1215  $image->recordUpload2( '', 'Upload of some lame SVG', 'Some lame SVG', [
1216  'size' => 12345,
1217  'width' => 240,
1218  'height' => 180,
1219  'bits' => 0,
1220  'media_type' => MEDIATYPE_DRAWING,
1221  'mime' => 'image/svg+xml',
1222  'metadata' => serialize( [] ),
1223  'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
1224  'fileExists' => true
1225  ], $this->db->timestamp( '20010115123500' ), $user );
1226 
1227  # This image will be blacklisted in [[MediaWiki:Bad image list]]
1228  $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) );
1229  $image->recordUpload2( '', 'zomgnotcensored', 'Borderline image', [
1230  'size' => 12345,
1231  'width' => 320,
1232  'height' => 240,
1233  'bits' => 24,
1234  'media_type' => MEDIATYPE_BITMAP,
1235  'mime' => 'image/jpeg',
1236  'metadata' => serialize( [] ),
1237  'sha1' => Wikimedia\base_convert( '3', 16, 36, 31 ),
1238  'fileExists' => true
1239  ], $this->db->timestamp( '20010115123500' ), $user );
1240 
1241  $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Video.ogv' ) );
1242  $image->recordUpload2( '', 'A pretty movie', 'Will it play', [
1243  'size' => 12345,
1244  'width' => 320,
1245  'height' => 240,
1246  'bits' => 0,
1247  'media_type' => MEDIATYPE_VIDEO,
1248  'mime' => 'application/ogg',
1249  'metadata' => serialize( [] ),
1250  'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
1251  'fileExists' => true
1252  ], $this->db->timestamp( '20010115123500' ), $user );
1253 
1254  $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Audio.oga' ) );
1255  $image->recordUpload2( '', 'An awesome hitsong', 'Will it play', [
1256  'size' => 12345,
1257  'width' => 0,
1258  'height' => 0,
1259  'bits' => 0,
1260  'media_type' => MEDIATYPE_AUDIO,
1261  'mime' => 'application/ogg',
1262  'metadata' => serialize( [] ),
1263  'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
1264  'fileExists' => true
1265  ], $this->db->timestamp( '20010115123500' ), $user );
1266 
1267  # A DjVu file
1268  $image = wfLocalFile( Title::makeTitle( NS_FILE, 'LoremIpsum.djvu' ) );
1269  $image->recordUpload2( '', 'Upload a DjVu', 'A DjVu', [
1270  'size' => 3249,
1271  'width' => 2480,
1272  'height' => 3508,
1273  'bits' => 0,
1274  'media_type' => MEDIATYPE_BITMAP,
1275  'mime' => 'image/vnd.djvu',
1276  'metadata' => '<?xml version="1.0" ?>
1277 <!DOCTYPE DjVuXML PUBLIC "-//W3C//DTD DjVuXML 1.1//EN" "pubtext/DjVuXML-s.dtd">
1278 <DjVuXML>
1279 <HEAD></HEAD>
1280 <BODY><OBJECT height="3508" width="2480">
1281 <PARAM name="DPI" value="300" />
1282 <PARAM name="GAMMA" value="2.2" />
1283 </OBJECT>
1284 <OBJECT height="3508" width="2480">
1285 <PARAM name="DPI" value="300" />
1286 <PARAM name="GAMMA" value="2.2" />
1287 </OBJECT>
1288 <OBJECT height="3508" width="2480">
1289 <PARAM name="DPI" value="300" />
1290 <PARAM name="GAMMA" value="2.2" />
1291 </OBJECT>
1292 <OBJECT height="3508" width="2480">
1293 <PARAM name="DPI" value="300" />
1294 <PARAM name="GAMMA" value="2.2" />
1295 </OBJECT>
1296 <OBJECT height="3508" width="2480">
1297 <PARAM name="DPI" value="300" />
1298 <PARAM name="GAMMA" value="2.2" />
1299 </OBJECT>
1300 </BODY>
1301 </DjVuXML>',
1302  'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
1303  'fileExists' => true
1304  ], $this->db->timestamp( '20010115123600' ), $user );
1305 
1306  return $this->createTeardownObject( $teardown, $nextTeardown );
1307  }
1308 
1315  private function teardownDatabase() {
1316  $this->checkSetupDone( 'setupDatabase' );
1317 
1318  $this->dbClone->destroy();
1319  $this->databaseSetupDone = false;
1320 
1321  if ( $this->useTemporaryTables ) {
1322  if ( $this->db->getType() == 'sqlite' ) {
1323  # Under SQLite the searchindex table is virtual and need
1324  # to be explicitly destroyed. See T31912
1325  # See also MediaWikiTestCase::destroyDB()
1326  wfDebug( __METHOD__ . " explicitly destroying sqlite virtual table parsertest_searchindex\n" );
1327  $this->db->query( "DROP TABLE `parsertest_searchindex`" );
1328  }
1329  # Don't need to do anything
1330  return;
1331  }
1332 
1333  $tables = $this->listTables();
1334 
1335  foreach ( $tables as $table ) {
1336  if ( $this->db->getType() == 'oracle' ) {
1337  $this->db->query( "DROP TABLE pt_$table DROP CONSTRAINTS" );
1338  } else {
1339  $this->db->query( "DROP TABLE `parsertest_$table`" );
1340  }
1341  }
1342 
1343  if ( $this->db->getType() == 'oracle' ) {
1344  $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
1345  }
1346  }
1347 
1353  private function setupUploadBackend() {
1354  global $IP;
1355 
1356  $repo = RepoGroup::singleton()->getLocalRepo();
1357  $base = $repo->getZonePath( 'public' );
1358  $backend = $repo->getBackend();
1359  $backend->prepare( [ 'dir' => "$base/3/3a" ] );
1360  $backend->store( [
1361  'src' => "$IP/tests/phpunit/data/parser/headbg.jpg",
1362  'dst' => "$base/3/3a/Foobar.jpg"
1363  ] );
1364  $backend->prepare( [ 'dir' => "$base/e/ea" ] );
1365  $backend->store( [
1366  'src' => "$IP/tests/phpunit/data/parser/wiki.png",
1367  'dst' => "$base/e/ea/Thumb.png"
1368  ] );
1369  $backend->prepare( [ 'dir' => "$base/0/09" ] );
1370  $backend->store( [
1371  'src' => "$IP/tests/phpunit/data/parser/headbg.jpg",
1372  'dst' => "$base/0/09/Bad.jpg"
1373  ] );
1374  $backend->prepare( [ 'dir' => "$base/5/5f" ] );
1375  $backend->store( [
1376  'src' => "$IP/tests/phpunit/data/parser/LoremIpsum.djvu",
1377  'dst' => "$base/5/5f/LoremIpsum.djvu"
1378  ] );
1379 
1380  // No helpful SVG file to copy, so make one ourselves
1381  $data = '<?xml version="1.0" encoding="utf-8"?>' .
1382  '<svg xmlns="http://www.w3.org/2000/svg"' .
1383  ' version="1.1" width="240" height="180"/>';
1384 
1385  $backend->prepare( [ 'dir' => "$base/f/ff" ] );
1386  $backend->quickCreate( [
1387  'content' => $data, 'dst' => "$base/f/ff/Foobar.svg"
1388  ] );
1389 
1390  return function () use ( $backend ) {
1391  if ( $backend instanceof MockFileBackend ) {
1392  // In memory backend, so dont bother cleaning them up.
1393  return;
1394  }
1395  $this->teardownUploadBackend();
1396  };
1397  }
1398 
1402  private function teardownUploadBackend() {
1403  if ( $this->keepUploads ) {
1404  return;
1405  }
1406 
1407  $repo = RepoGroup::singleton()->getLocalRepo();
1408  $public = $repo->getZonePath( 'public' );
1409 
1410  $this->deleteFiles(
1411  [
1412  "$public/3/3a/Foobar.jpg",
1413  "$public/e/ea/Thumb.png",
1414  "$public/0/09/Bad.jpg",
1415  "$public/5/5f/LoremIpsum.djvu",
1416  "$public/f/ff/Foobar.svg",
1417  "$public/0/00/Video.ogv",
1418  "$public/4/41/Audio.oga",
1419  ]
1420  );
1421  }
1422 
1427  private function deleteFiles( $files ) {
1428  // Delete the files
1429  $backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
1430  foreach ( $files as $file ) {
1431  $backend->delete( [ 'src' => $file ], [ 'force' => 1 ] );
1432  }
1433 
1434  // Delete the parent directories
1435  foreach ( $files as $file ) {
1436  $tmp = FileBackend::parentStoragePath( $file );
1437  while ( $tmp ) {
1438  if ( !$backend->clean( [ 'dir' => $tmp ] )->isOK() ) {
1439  break;
1440  }
1441  $tmp = FileBackend::parentStoragePath( $tmp );
1442  }
1443  }
1444  }
1445 
1451  public function addArticles( $articles ) {
1453  $setup = [];
1454  $teardown = [];
1455 
1456  // Be sure ParserTestRunner::addArticle has correct language set,
1457  // so that system messages get into the right language cache
1458  if ( $wgContLang->getCode() !== 'en' ) {
1459  $setup['wgLanguageCode'] = 'en';
1460  $setup['wgContLang'] = Language::factory( 'en' );
1461  }
1462 
1463  // Add special namespaces, in case that hasn't been done by staticSetup() yet
1464  $this->appendNamespaceSetup( $setup, $teardown );
1465 
1466  // wgCapitalLinks obviously needs initialisation
1467  $setup['wgCapitalLinks'] = true;
1468 
1469  $teardown[] = $this->executeSetupSnippets( $setup );
1470 
1471  foreach ( $articles as $info ) {
1472  $this->addArticle( $info['name'], $info['text'], $info['file'], $info['line'] );
1473  }
1474 
1475  // Wipe WANObjectCache process cache, which is invalidated by article insertion
1476  // due to T144706
1477  ObjectCache::getMainWANInstance()->clearProcessCache();
1478 
1479  $this->executeSetupSnippets( $teardown );
1480  }
1481 
1491  private function addArticle( $name, $text, $file, $line ) {
1492  $text = self::chomp( $text );
1493  $name = self::chomp( $name );
1494 
1496  wfDebug( __METHOD__ . ": adding $name" );
1497 
1498  if ( is_null( $title ) ) {
1499  throw new MWException( "invalid title '$name' at $file:$line\n" );
1500  }
1501 
1503  $page->loadPageData( 'fromdbmaster' );
1504 
1505  if ( $page->exists() ) {
1506  throw new MWException( "duplicate article '$name' at $file:$line\n" );
1507  }
1508 
1509  // Use mock parser, to make debugging of actual parser tests simpler.
1510  // But initialise the MessageCache clone first, don't let MessageCache
1511  // get a reference to the mock object.
1512  MessageCache::singleton()->getParser();
1513  $restore = $this->executeSetupSnippets( [ 'wgParser' => new ParserTestMockParser ] );
1514  $status = $page->doEditContent(
1516  '',
1518  );
1519  $restore();
1520 
1521  if ( !$status->isOK() ) {
1522  throw new MWException( $status->getWikiText( false, false, 'en' ) );
1523  }
1524 
1525  // The RepoGroup cache is invalidated by the creation of file redirects
1526  if ( $title->inNamespace( NS_FILE ) ) {
1527  RepoGroup::singleton()->clearCache( $title );
1528  }
1529  }
1530 
1537  public function requireHook( $name ) {
1538  global $wgParser;
1539 
1540  $wgParser->firstCallInit(); // make sure hooks are loaded.
1541  if ( isset( $wgParser->mTagHooks[$name] ) ) {
1542  return true;
1543  } else {
1544  $this->recorder->warning( " This test suite requires the '$name' hook " .
1545  "extension, skipping." );
1546  return false;
1547  }
1548  }
1549 
1556  public function requireFunctionHook( $name ) {
1557  global $wgParser;
1558 
1559  $wgParser->firstCallInit(); // make sure hooks are loaded.
1560 
1561  if ( isset( $wgParser->mFunctionHooks[$name] ) ) {
1562  return true;
1563  } else {
1564  $this->recorder->warning( " This test suite requires the '$name' function " .
1565  "hook extension, skipping." );
1566  return false;
1567  }
1568  }
1569 
1576  public function requireTransparentHook( $name ) {
1577  global $wgParser;
1578 
1579  $wgParser->firstCallInit(); // make sure hooks are loaded.
1580 
1581  if ( isset( $wgParser->mTransparentTagHooks[$name] ) ) {
1582  return true;
1583  } else {
1584  $this->recorder->warning( " This test suite requires the '$name' transparent " .
1585  "hook extension, skipping.\n" );
1586  return false;
1587  }
1588  }
1589 
1594  static function getFakeTimestamp( &$parser, &$ts ) {
1595  $ts = 123; // parsed as '1970-01-01T00:02:03Z'
1596  return true;
1597  }
1598 }
$context
error also a ContextSource you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition: hooks.txt:2612
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:265
RepoGroup\singleton
static singleton()
Get a RepoGroup instance.
Definition: RepoGroup.php:59
MEDIATYPE_AUDIO
const MEDIATYPE_AUDIO
Definition: defines.php:32
MWNamespace\getValidNamespaces
static getValidNamespaces()
Returns an array of the namespaces (by integer id) that exist on the wiki.
Definition: MWNamespace.php:264
$tables
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist & $tables
Definition: hooks.txt:990
LockManagerGroup\destroySingletons
static destroySingletons()
Destroy the singleton instances.
Definition: LockManagerGroup.php:66
$wgParser
$wgParser
Definition: Setup.php:796
HashBagOStuff
Simple store for keeping values in an associative array for the current process.
Definition: HashBagOStuff.php:31
ParserTestResult
Represent the result of a parser test.
Definition: ParserTestResult.php:14
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
$opt
$opt
Definition: postprocess-phan.php:115
EDIT_INTERNAL
const EDIT_INTERNAL
Definition: Defines.php:157
captcha-old.count
count
Definition: captcha-old.py:225
wfGetLB
wfGetLB( $wiki=false)
Get a load balancer object.
Definition: GlobalFunctions.php:3073
$result
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. 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:Array with elements of the form "language:title" in the order that they will be output. & $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:1954
$status
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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:1049
ParserTestResultNormalizer\normalize
static normalize( $text, $funcs)
Definition: ParserTestResultNormalizer.php:10
use
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 use
Definition: MIT-LICENSE.txt:10
ObjectCache\$instances
static BagOStuff[] $instances
Map of (id => BagOStuff)
Definition: ObjectCache.php:82
MEDIATYPE_DRAWING
const MEDIATYPE_DRAWING
Definition: defines.php:30
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:246
TestRecorder\warning
warning( $message)
Show a warning to the user.
Definition: TestRecorder.php:72
NS_FILE
const NS_FILE
Definition: Defines.php:68
serialize
serialize()
Definition: ApiMessage.php:177
MWTidy\factory
static factory(array $config)
Create a new Tidy driver object from configuration.
Definition: MWTidy.php:124
MockFileBackend
Class simulating a backend store.
Definition: MockFileBackend.php:31
$s
$s
Definition: mergeMessageFileList.php:188
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:304
Hooks\clear
static clear( $name)
Clears hooks registered via Hooks::register().
Definition: Hooks.php:66
User
User
Definition: All_system_messages.txt:425
$base
$base
Definition: generateLocalAutoload.php:10
ContextSource\getUser
getUser()
Get the User object.
Definition: ContextSource.php:133
$wgDBprefix
$wgDBprefix
Table name prefix.
Definition: DefaultSettings.php:1827
php
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
User\createNew
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:4081
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:40
TidySupport
Initialize and detect the tidy support.
Definition: TidySupport.php:26
FormatJson\decode
static decode( $value, $assoc=false)
Decodes a JSON string.
Definition: FormatJson.php:187
MWException
MediaWiki exception.
Definition: MWException.php:26
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:934
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:120
$content
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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:1049
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:3060
$page
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached $page
Definition: hooks.txt:2536
$matches
$matches
Definition: NoLocalSettings.php:24
TestRecorder
Interface to record parser test results.
Definition: TestRecorder.php:35
$IP
$IP
Definition: update.php:3
$wgObjectCaches
$wgObjectCaches
Advanced object cache configuration.
Definition: DefaultSettings.php:2272
MockMediaHandlerFactory
Replace all media handlers with a mock.
Definition: MockMediaHandlerFactory.php:31
$parser
do that in ParserLimitReportFormat instead $parser
Definition: hooks.txt:2536
$output
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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:1049
MEDIATYPE_BITMAP
const MEDIATYPE_BITMAP
Definition: defines.php:28
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:514
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
$wgFileBackends
$wgFileBackends
File backend structure configuration.
Definition: DefaultSettings.php:640
$GLOBALS
$GLOBALS['wgAutoloadClasses']['LocalisationUpdate']
Definition: Autoload.php:10
DB_MASTER
const DB_MASTER
Definition: defines.php:26
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:999
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
MessageCache\singleton
static singleton()
Get the signleton instance of this class.
Definition: MessageCache.php:113
$services
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title after the basic globals have been set but before ordinary actions take place or wrap services the preferred way to define a new service is the $wgServiceWiringFiles array $services
Definition: hooks.txt:2179
ContentHandler\makeContent
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
Definition: ContentHandler.php:129
$image
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 $image
Definition: hooks.txt:783
MagicWord\clearCache
static clearCache()
Clear the self::$mObjects variable For use in parser tests.
Definition: MagicWord.php:320
MWTidy\setInstance
static setInstance( $instance)
Set the driver to be used.
Definition: MWTidy.php:156
$line
$line
Definition: cdb.php:58
RepoGroup\destroySingleton
static destroySingleton()
Destroy the singleton instance, so that a new one will be created next time singleton() is called.
Definition: RepoGroup.php:73
wfWikiID
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
Definition: GlobalFunctions.php:3011
ParserTestParserHook\setup
static setup(&$parser)
Definition: ParserTestParserHook.php:30
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2122
Hooks\register
static register( $name, $callback)
Attach an event handler to a given hook.
Definition: Hooks.php:49
$value
$value
Definition: styleTest.css.php:45
ParserOptions\newFromContext
static newFromContext(IContextSource $context)
Get a ParserOptions object from a IContextSource object.
Definition: ParserOptions.php:726
ParserTestMockParser
A parser used during article insertion which does nothing, to avoid unnecessary log noise and other i...
Definition: ParserTestMockParser.php:7
RequestContext\getMain
static getMain()
Static methods.
Definition: RequestContext.php:468
Linker\formatComment
static formatComment( $comment, $title=null, $local=false, $wikiId=null)
This function is called by all recent changes variants, by the page history, and by the user contribu...
Definition: Linker.php:1094
EDIT_NEW
const EDIT_NEW
Definition: Defines.php:150
TestFileReader\read
static read( $file, array $options=[])
Definition: TestFileReader.php:38
MediaWikiTestCase\DB_PREFIX
const DB_PREFIX
Table name prefixes.
Definition: MediaWikiTestCase.php:102
wfTempDir
wfTempDir()
Tries to get the system directory for temporary files.
Definition: GlobalFunctions.php:2061
CloneDatabase
Definition: CloneDatabase.php:29
FSFileBackend
Class for a file system (FS) based file backend.
Definition: FSFileBackend.php:42
MEDIATYPE_VIDEO
const MEDIATYPE_VIDEO
Definition: defines.php:35
MediaWikiTestCase\ORA_DB_PREFIX
const ORA_DB_PREFIX
Definition: MediaWikiTestCase.php:103
ObjectCache\getMainWANInstance
static getMainWANInstance()
Get the main WAN cache object.
Definition: ObjectCache.php:370
LinkCache\singleton
static singleton()
Get an instance of this class.
Definition: LinkCache.php:67
$section
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:2929
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
MWNamespace\getCanonicalNamespaces
static getCanonicalNamespaces( $rebuild=false)
Returns array of all defined namespaces with their canonical (English) names.
Definition: MWNamespace.php:207
Wikimedia
RepoGroup
Prioritized list of file repositories.
Definition: RepoGroup.php:29
true
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 true
Definition: hooks.txt:1956
Language\factory
static factory( $code)
Get a cached or new language object for a given language code.
Definition: Language.php:183
MediaWikiServices
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
wfLocalFile
wfLocalFile( $title)
Get an object referring to a locally registered file.
Definition: GlobalFunctions.php:3112
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
MWTidy\destroySingleton
static destroySingleton()
Destroy the current singleton instance.
Definition: MWTidy.php:163
wfGetCaller
wfGetCaller( $level=2)
Get the name of the function which called this function wfGetCaller( 1 ) is the function with the wfG...
Definition: GlobalFunctions.php:1561
$options
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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:1049
RepoGroup\setSingleton
static setSingleton( $instance)
Set the singleton instance to a given object Used by extensions which hook into the Repo chain.
Definition: RepoGroup.php:85
CACHE_DB
const CACHE_DB
Definition: Defines.php:101
array
the array() calling protocol came about after MediaWiki 1.4rc1.
FileBackend\parentStoragePath
static parentStoragePath( $storagePath)
Get the parent storage directory of a storage path.
Definition: FileBackend.php:1493
$wgContLang
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the content language as $wgContLang
Definition: design.txt:56
$out
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:783
MessageCache\destroyInstance
static destroyInstance()
Destroy the singleton instance.
Definition: MessageCache.php:135