MediaWiki REL1_29
MediaWikiTestCase.php
Go to the documentation of this file.
1<?php
2
7use Psr\Log\LoggerInterface;
8use Wikimedia\TestingAccessWrapper;
9
13abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
14
22 private static $serviceLocator = null;
23
36 private $called = [];
37
42 public static $users;
43
50 protected $db;
51
56 protected $tablesUsed = []; // tables with data
57
58 private static $useTemporaryTables = true;
59 private static $reuseDB = false;
60 private static $dbSetup = false;
61 private static $oldTablePrefix = '';
62
69
76 private $tmpFiles = [];
77
84 private $mwGlobals = [];
85
91 private $mwGlobalsToUnset = [];
92
97 private $loggers = [];
98
102 const DB_PREFIX = 'unittest_';
103 const ORA_DB_PREFIX = 'ut_';
104
109 protected $supportedDBs = [
110 'mysql',
111 'sqlite',
112 'postgres',
113 'oracle'
114 ];
115
116 public function __construct( $name = null, array $data = [], $dataName = '' ) {
117 parent::__construct( $name, $data, $dataName );
118
119 $this->backupGlobals = false;
120 $this->backupStaticAttributes = false;
121 }
122
123 public function __destruct() {
124 // Complain if self::setUp() was called, but not self::tearDown()
125 // $this->called['setUp'] will be checked by self::testMediaWikiTestCaseParentSetupCalled()
126 if ( isset( $this->called['setUp'] ) && !isset( $this->called['tearDown'] ) ) {
127 throw new MWException( static::class . "::tearDown() must call parent::tearDown()" );
128 }
129 }
130
131 public static function setUpBeforeClass() {
132 parent::setUpBeforeClass();
133
134 // Get the service locator, and reset services if it's not done already
135 self::$serviceLocator = self::prepareServices( new GlobalVarConfig() );
136 }
137
146 public static function getTestUser( $groups = [] ) {
148 }
149
158 public static function getMutableTestUser( $groups = [] ) {
159 return TestUserRegistry::getMutableTestUser( __CLASS__, $groups );
160 }
161
170 public static function getTestSysop() {
171 return self::getTestUser( [ 'sysop', 'bureaucrat' ] );
172 }
173
193 public static function prepareServices( Config $bootstrapConfig ) {
194 static $services = null;
195
196 if ( !$services ) {
197 $services = self::resetGlobalServices( $bootstrapConfig );
198 }
199 return $services;
200 }
201
215 protected static function resetGlobalServices( Config $bootstrapConfig = null ) {
216 $oldServices = MediaWikiServices::getInstance();
217 $oldConfigFactory = $oldServices->getConfigFactory();
218
219 $testConfig = self::makeTestConfig( $bootstrapConfig );
220
221 MediaWikiServices::resetGlobalInstance( $testConfig );
222
223 $serviceLocator = MediaWikiServices::getInstance();
224 self::installTestServices(
225 $oldConfigFactory,
227 );
228 return $serviceLocator;
229 }
230
240 private static function makeTestConfig(
241 Config $baseConfig = null,
242 Config $customOverrides = null
243 ) {
244 $defaultOverrides = new HashConfig();
245
246 if ( !$baseConfig ) {
247 $baseConfig = MediaWikiServices::getInstance()->getBootstrapConfig();
248 }
249
250 /* Some functions require some kind of caching, and will end up using the db,
251 * which we can't allow, as that would open a new connection for mysql.
252 * Replace with a HashBag. They would not be going to persist anyway.
253 */
254 $hashCache = [ 'class' => 'HashBagOStuff', 'reportDupes' => false ];
255 $objectCaches = [
256 CACHE_DB => $hashCache,
257 CACHE_ACCEL => $hashCache,
258 CACHE_MEMCACHED => $hashCache,
259 'apc' => $hashCache,
260 'apcu' => $hashCache,
261 'xcache' => $hashCache,
262 'wincache' => $hashCache,
263 ] + $baseConfig->get( 'ObjectCaches' );
264
265 $defaultOverrides->set( 'ObjectCaches', $objectCaches );
266 $defaultOverrides->set( 'MainCacheType', CACHE_NONE );
267 $defaultOverrides->set( 'JobTypeConf', [ 'default' => [ 'class' => 'JobQueueMemory' ] ] );
268
269 // Use a fast hash algorithm to hash passwords.
270 $defaultOverrides->set( 'PasswordDefault', 'A' );
271
272 $testConfig = $customOverrides
273 ? new MultiConfig( [ $customOverrides, $defaultOverrides, $baseConfig ] )
274 : new MultiConfig( [ $defaultOverrides, $baseConfig ] );
275
276 return $testConfig;
277 }
278
285 private static function installTestServices(
286 ConfigFactory $oldConfigFactory,
287 MediaWikiServices $newServices
288 ) {
289 // Use bootstrap config for all configuration.
290 // This allows config overrides via global variables to take effect.
291 $bootstrapConfig = $newServices->getBootstrapConfig();
292 $newServices->resetServiceForTesting( 'ConfigFactory' );
293 $newServices->redefineService(
294 'ConfigFactory',
295 self::makeTestConfigFactoryInstantiator(
296 $oldConfigFactory,
297 [ 'main' => $bootstrapConfig ]
298 )
299 );
300 }
301
308 private static function makeTestConfigFactoryInstantiator(
309 ConfigFactory $oldFactory,
310 array $configurations
311 ) {
312 return function( MediaWikiServices $services ) use ( $oldFactory, $configurations ) {
313 $factory = new ConfigFactory();
314
315 // clone configurations from $oldFactory that are not overwritten by $configurations
316 $namesToClone = array_diff(
317 $oldFactory->getConfigNames(),
318 array_keys( $configurations )
319 );
320
321 foreach ( $namesToClone as $name ) {
322 $factory->register( $name, $oldFactory->makeConfig( $name ) );
323 }
324
325 foreach ( $configurations as $name => $config ) {
326 $factory->register( $name, $config );
327 }
328
329 return $factory;
330 };
331 }
332
344 private function doLightweightServiceReset() {
346
348 ObjectCache::clear();
349 $services = MediaWikiServices::getInstance();
350 $services->resetServiceForTesting( 'MainObjectStash' );
351 $services->resetServiceForTesting( 'LocalServerObjectCache' );
352 $services->getMainWANObjectCache()->clearProcessCache();
354
355 // TODO: move global state into MediaWikiServices
357 if ( session_id() !== '' ) {
358 session_write_close();
359 session_id( '' );
360 }
361
362 $wgRequest = new FauxRequest();
363 MediaWiki\Session\SessionManager::resetCache();
364 }
365
366 public function run( PHPUnit_Framework_TestResult $result = null ) {
367 // Reset all caches between tests.
369
370 $needsResetDB = false;
371
372 if ( !self::$dbSetup || $this->needsDB() ) {
373 // set up a DB connection for this test to use
374
375 self::$useTemporaryTables = !$this->getCliArg( 'use-normal-tables' );
376 self::$reuseDB = $this->getCliArg( 'reuse-db' );
377
378 $this->db = wfGetDB( DB_MASTER );
379
380 $this->checkDbIsSupported();
381
382 if ( !self::$dbSetup ) {
383 $this->setupAllTestDBs();
384 $this->addCoreDBData();
385
386 if ( ( $this->db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) {
387 $this->resetDB( $this->db, $this->tablesUsed );
388 }
389 }
390
391 // TODO: the DB setup should be done in setUpBeforeClass(), so the test DB
392 // is available in subclass's setUpBeforeClass() and setUp() methods.
393 // This would also remove the need for the HACK that is oncePerClass().
394 if ( $this->oncePerClass() ) {
395 $this->addDBDataOnce();
396 }
397
398 $this->addDBData();
399 $needsResetDB = true;
400 }
401
402 parent::run( $result );
403
404 if ( $needsResetDB ) {
405 $this->resetDB( $this->db, $this->tablesUsed );
406 }
407 }
408
412 private function oncePerClass() {
413 // Remember current test class in the database connection,
414 // so we know when we need to run addData.
415
416 $class = static::class;
417
418 $first = !isset( $this->db->_hasDataForTestClass )
419 || $this->db->_hasDataForTestClass !== $class;
420
421 $this->db->_hasDataForTestClass = $class;
422 return $first;
423 }
424
430 public function usesTemporaryTables() {
431 return self::$useTemporaryTables;
432 }
433
443 protected function getNewTempFile() {
444 $fileName = tempnam( wfTempDir(), 'MW_PHPUnit_' . static::class . '_' );
445 $this->tmpFiles[] = $fileName;
446
447 return $fileName;
448 }
449
460 protected function getNewTempDirectory() {
461 // Starting of with a temporary /file/.
462 $fileName = $this->getNewTempFile();
463
464 // Converting the temporary /file/ to a /directory/
465 // The following is not atomic, but at least we now have a single place,
466 // where temporary directory creation is bundled and can be improved
467 unlink( $fileName );
468 $this->assertTrue( wfMkdirParents( $fileName ) );
469
470 return $fileName;
471 }
472
473 protected function setUp() {
474 parent::setUp();
475 $this->called['setUp'] = true;
476
477 $this->phpErrorLevel = intval( ini_get( 'error_reporting' ) );
478
479 // Cleaning up temporary files
480 foreach ( $this->tmpFiles as $fileName ) {
481 if ( is_file( $fileName ) || ( is_link( $fileName ) ) ) {
482 unlink( $fileName );
483 } elseif ( is_dir( $fileName ) ) {
484 wfRecursiveRemoveDir( $fileName );
485 }
486 }
487
488 if ( $this->needsDB() && $this->db ) {
489 // Clean up open transactions
490 while ( $this->db->trxLevel() > 0 ) {
491 $this->db->rollback( __METHOD__, 'flush' );
492 }
493 // Check for unsafe queries
494 if ( $this->db->getType() === 'mysql' ) {
495 $this->db->query( "SET sql_mode = 'STRICT_ALL_TABLES'" );
496 }
497 }
498
499 DeferredUpdates::clearPendingUpdates();
500 ObjectCache::getMainWANInstance()->clearProcessCache();
501
502 // XXX: reset maintenance triggers
503 // Hook into period lag checks which often happen in long-running scripts
504 $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
506
507 ob_start( 'MediaWikiTestCase::wfResetOutputBuffersBarrier' );
508 }
509
510 protected function addTmpFiles( $files ) {
511 $this->tmpFiles = array_merge( $this->tmpFiles, (array)$files );
512 }
513
514 protected function tearDown() {
516
517 $status = ob_get_status();
518 if ( isset( $status['name'] ) &&
519 $status['name'] === 'MediaWikiTestCase::wfResetOutputBuffersBarrier'
520 ) {
521 ob_end_flush();
522 }
523
524 $this->called['tearDown'] = true;
525 // Cleaning up temporary files
526 foreach ( $this->tmpFiles as $fileName ) {
527 if ( is_file( $fileName ) || ( is_link( $fileName ) ) ) {
528 unlink( $fileName );
529 } elseif ( is_dir( $fileName ) ) {
530 wfRecursiveRemoveDir( $fileName );
531 }
532 }
533
534 if ( $this->needsDB() && $this->db ) {
535 // Clean up open transactions
536 while ( $this->db->trxLevel() > 0 ) {
537 $this->db->rollback( __METHOD__, 'flush' );
538 }
539 if ( $this->db->getType() === 'mysql' ) {
540 $this->db->query( "SET sql_mode = " . $this->db->addQuotes( $wgSQLMode ) );
541 }
542 }
543
544 // Restore mw globals
545 foreach ( $this->mwGlobals as $key => $value ) {
546 $GLOBALS[$key] = $value;
547 }
548 foreach ( $this->mwGlobalsToUnset as $value ) {
549 unset( $GLOBALS[$value] );
550 }
551 $this->mwGlobals = [];
552 $this->mwGlobalsToUnset = [];
553 $this->restoreLoggers();
554
555 if ( self::$serviceLocator && MediaWikiServices::getInstance() !== self::$serviceLocator ) {
556 MediaWikiServices::forceGlobalInstance( self::$serviceLocator );
557 }
558
559 // TODO: move global state into MediaWikiServices
561 if ( session_id() !== '' ) {
562 session_write_close();
563 session_id( '' );
564 }
565 $wgRequest = new FauxRequest();
566 MediaWiki\Session\SessionManager::resetCache();
567 MediaWiki\Auth\AuthManager::resetCache();
568
569 $phpErrorLevel = intval( ini_get( 'error_reporting' ) );
570
571 if ( $phpErrorLevel !== $this->phpErrorLevel ) {
572 ini_set( 'error_reporting', $this->phpErrorLevel );
573
574 $oldHex = strtoupper( dechex( $this->phpErrorLevel ) );
575 $newHex = strtoupper( dechex( $phpErrorLevel ) );
576 $message = "PHP error_reporting setting was left dirty: "
577 . "was 0x$oldHex before test, 0x$newHex after test!";
578
579 $this->fail( $message );
580 }
581
582 parent::tearDown();
583 }
584
594 $this->assertArrayHasKey( 'setUp', $this->called,
595 static::class . '::setUp() must call parent::setUp()'
596 );
597 }
598
608 protected function setService( $name, $object ) {
609 // If we did not yet override the service locator, so so now.
610 if ( MediaWikiServices::getInstance() === self::$serviceLocator ) {
611 $this->overrideMwServices();
612 }
613
614 MediaWikiServices::getInstance()->disableService( $name );
615 MediaWikiServices::getInstance()->redefineService(
616 $name,
617 function () use ( $object ) {
618 return $object;
619 }
620 );
621 }
622
658 protected function setMwGlobals( $pairs, $value = null ) {
659 if ( is_string( $pairs ) ) {
660 $pairs = [ $pairs => $value ];
661 }
662
663 $this->stashMwGlobals( array_keys( $pairs ) );
664
665 foreach ( $pairs as $key => $value ) {
666 $GLOBALS[$key] = $value;
667 }
668 }
669
678 private static function canShallowCopy( $value ) {
679 if ( is_scalar( $value ) || $value === null ) {
680 return true;
681 }
682 if ( is_array( $value ) ) {
683 foreach ( $value as $subValue ) {
684 if ( !is_scalar( $subValue ) && $subValue !== null ) {
685 return false;
686 }
687 }
688 return true;
689 }
690 return false;
691 }
692
710 protected function stashMwGlobals( $globalKeys ) {
711 if ( is_string( $globalKeys ) ) {
712 $globalKeys = [ $globalKeys ];
713 }
714
715 foreach ( $globalKeys as $globalKey ) {
716 // NOTE: make sure we only save the global once or a second call to
717 // setMwGlobals() on the same global would override the original
718 // value.
719 if (
720 !array_key_exists( $globalKey, $this->mwGlobals ) &&
721 !array_key_exists( $globalKey, $this->mwGlobalsToUnset )
722 ) {
723 if ( !array_key_exists( $globalKey, $GLOBALS ) ) {
724 $this->mwGlobalsToUnset[$globalKey] = $globalKey;
725 continue;
726 }
727 // NOTE: we serialize then unserialize the value in case it is an object
728 // this stops any objects being passed by reference. We could use clone
729 // and if is_object but this does account for objects within objects!
730 if ( self::canShallowCopy( $GLOBALS[$globalKey] ) ) {
731 $this->mwGlobals[$globalKey] = $GLOBALS[$globalKey];
732 } elseif (
733 // Many MediaWiki types are safe to clone. These are the
734 // ones that are most commonly stashed.
735 $GLOBALS[$globalKey] instanceof Language ||
736 $GLOBALS[$globalKey] instanceof User ||
737 $GLOBALS[$globalKey] instanceof FauxRequest
738 ) {
739 $this->mwGlobals[$globalKey] = clone $GLOBALS[$globalKey];
740 } else {
741 try {
742 $this->mwGlobals[$globalKey] = unserialize( serialize( $GLOBALS[$globalKey] ) );
743 } catch ( Exception $e ) {
744 $this->mwGlobals[$globalKey] = $GLOBALS[$globalKey];
745 }
746 }
747 }
748 }
749 }
750
766 protected function mergeMwGlobalArrayValue( $name, $values ) {
767 if ( !isset( $GLOBALS[$name] ) ) {
768 $merged = $values;
769 } else {
770 if ( !is_array( $GLOBALS[$name] ) ) {
771 throw new MWException( "MW global $name is not an array." );
772 }
773
774 // NOTE: do not use array_merge, it screws up for numeric keys.
775 $merged = $GLOBALS[$name];
776 foreach ( $values as $k => $v ) {
777 $merged[$k] = $v;
778 }
779 }
780
781 $this->setMwGlobals( $name, $merged );
782 }
783
798 protected function overrideMwServices( Config $configOverrides = null, array $services = [] ) {
799 if ( !$configOverrides ) {
800 $configOverrides = new HashConfig();
801 }
802
803 $oldInstance = MediaWikiServices::getInstance();
804 $oldConfigFactory = $oldInstance->getConfigFactory();
805
806 $testConfig = self::makeTestConfig( null, $configOverrides );
807 $newInstance = new MediaWikiServices( $testConfig );
808
809 // Load the default wiring from the specified files.
810 // NOTE: this logic mirrors the logic in MediaWikiServices::newInstance.
811 $wiringFiles = $testConfig->get( 'ServiceWiringFiles' );
812 $newInstance->loadWiringFiles( $wiringFiles );
813
814 // Provide a traditional hook point to allow extensions to configure services.
815 Hooks::run( 'MediaWikiServices', [ $newInstance ] );
816
817 foreach ( $services as $name => $callback ) {
818 $newInstance->redefineService( $name, $callback );
819 }
820
821 self::installTestServices(
822 $oldConfigFactory,
823 $newInstance
824 );
825 MediaWikiServices::forceGlobalInstance( $newInstance );
826
827 return $newInstance;
828 }
829
834 public function setUserLang( $lang ) {
835 RequestContext::getMain()->setLanguage( $lang );
836 $this->setMwGlobals( 'wgLang', RequestContext::getMain()->getLanguage() );
837 }
838
843 public function setContentLang( $lang ) {
844 if ( $lang instanceof Language ) {
845 $langCode = $lang->getCode();
846 $langObj = $lang;
847 } else {
848 $langCode = $lang;
849 $langObj = Language::factory( $langCode );
850 }
851 $this->setMwGlobals( [
852 'wgLanguageCode' => $langCode,
853 'wgContLang' => $langObj,
854 ] );
855 }
856
863 protected function setLogger( $channel, LoggerInterface $logger ) {
864 // TODO: Once loggers are managed by MediaWikiServices, use
865 // overrideMwServices() to set loggers.
866
867 $provider = LoggerFactory::getProvider();
868 $wrappedProvider = TestingAccessWrapper::newFromObject( $provider );
869 $singletons = $wrappedProvider->singletons;
870 if ( $provider instanceof MonologSpi ) {
871 if ( !isset( $this->loggers[$channel] ) ) {
872 $this->loggers[$channel] = isset( $singletons['loggers'][$channel] )
873 ? $singletons['loggers'][$channel] : null;
874 }
875 $singletons['loggers'][$channel] = $logger;
876 } elseif ( $provider instanceof LegacySpi ) {
877 if ( !isset( $this->loggers[$channel] ) ) {
878 $this->loggers[$channel] = isset( $singletons[$channel] ) ? $singletons[$channel] : null;
879 }
880 $singletons[$channel] = $logger;
881 } else {
882 throw new LogicException( __METHOD__ . ': setting a logger for ' . get_class( $provider )
883 . ' is not implemented' );
884 }
885 $wrappedProvider->singletons = $singletons;
886 }
887
892 private function restoreLoggers() {
893 $provider = LoggerFactory::getProvider();
894 $wrappedProvider = TestingAccessWrapper::newFromObject( $provider );
895 $singletons = $wrappedProvider->singletons;
896 foreach ( $this->loggers as $channel => $logger ) {
897 if ( $provider instanceof MonologSpi ) {
898 if ( $logger === null ) {
899 unset( $singletons['loggers'][$channel] );
900 } else {
901 $singletons['loggers'][$channel] = $logger;
902 }
903 } elseif ( $provider instanceof LegacySpi ) {
904 if ( $logger === null ) {
905 unset( $singletons[$channel] );
906 } else {
907 $singletons[$channel] = $logger;
908 }
909 }
910 }
911 $wrappedProvider->singletons = $singletons;
912 $this->loggers = [];
913 }
914
919 public function dbPrefix() {
920 return $this->db->getType() == 'oracle' ? self::ORA_DB_PREFIX : self::DB_PREFIX;
921 }
922
927 public function needsDB() {
928 # if the test says it uses database tables, it needs the database
929 if ( $this->tablesUsed ) {
930 return true;
931 }
932
933 # if the test says it belongs to the Database group, it needs the database
934 $rc = new ReflectionClass( $this );
935 if ( preg_match( '/@group +Database/im', $rc->getDocComment() ) ) {
936 return true;
937 }
938
939 return false;
940 }
941
953 protected function insertPage(
954 $pageName,
955 $text = 'Sample page for unit test.',
956 $namespace = null
957 ) {
958 if ( is_string( $pageName ) ) {
959 $title = Title::newFromText( $pageName, $namespace );
960 } else {
961 $title = $pageName;
962 }
963
964 $user = static::getTestSysop()->getUser();
965 $comment = __METHOD__ . ': Sample page for unit test.';
966
967 // Avoid memory leak...?
968 // LinkCache::singleton()->clear();
969 // Maybe. But doing this absolutely breaks $title->isRedirect() when called during unit tests....
970
972 $page->doEditContent( ContentHandler::makeContent( $text, $title ), $comment, 0, false, $user );
973
974 return [
975 'title' => $title,
976 'id' => $page->getId(),
977 ];
978 }
979
995 public function addDBDataOnce() {
996 }
997
1007 public function addDBData() {
1008 }
1009
1010 private function addCoreDBData() {
1011 if ( $this->db->getType() == 'oracle' ) {
1012
1013 # Insert 0 user to prevent FK violations
1014 # Anonymous user
1015 if ( !$this->db->selectField( 'user', '1', [ 'user_id' => 0 ] ) ) {
1016 $this->db->insert( 'user', [
1017 'user_id' => 0,
1018 'user_name' => 'Anonymous' ], __METHOD__, [ 'IGNORE' ] );
1019 }
1020
1021 # Insert 0 page to prevent FK violations
1022 # Blank page
1023 if ( !$this->db->selectField( 'page', '1', [ 'page_id' => 0 ] ) ) {
1024 $this->db->insert( 'page', [
1025 'page_id' => 0,
1026 'page_namespace' => 0,
1027 'page_title' => ' ',
1028 'page_restrictions' => null,
1029 'page_is_redirect' => 0,
1030 'page_is_new' => 0,
1031 'page_random' => 0,
1032 'page_touched' => $this->db->timestamp(),
1033 'page_latest' => 0,
1034 'page_len' => 0 ], __METHOD__, [ 'IGNORE' ] );
1035 }
1036 }
1037
1038 User::resetIdByNameCache();
1039
1040 // Make sysop user
1041 $user = static::getTestSysop()->getUser();
1042
1043 // Make 1 page with 1 revision
1044 $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
1045 if ( $page->getId() == 0 ) {
1046 $page->doEditContent(
1047 new WikitextContent( 'UTContent' ),
1048 'UTPageSummary',
1049 EDIT_NEW,
1050 false,
1051 $user
1052 );
1053
1054 // doEditContent() probably started the session via
1055 // User::loadFromSession(). Close it now.
1056 if ( session_id() !== '' ) {
1057 session_write_close();
1058 session_id( '' );
1059 }
1060 }
1061 }
1062
1070 public static function teardownTestDB() {
1072
1073 if ( !self::$dbSetup ) {
1074 return;
1075 }
1076
1077 foreach ( $wgJobClasses as $type => $class ) {
1078 // Delete any jobs under the clone DB (or old prefix in other stores)
1079 JobQueueGroup::singleton()->get( $type )->delete();
1080 }
1081
1082 CloneDatabase::changePrefix( self::$oldTablePrefix );
1083
1084 self::$oldTablePrefix = false;
1085 self::$dbSetup = false;
1086 }
1087
1101 protected static function setupDatabaseWithTestPrefix( IMaintainableDatabase $db, $prefix ) {
1102 $tablesCloned = self::listTables( $db );
1103 $dbClone = new CloneDatabase( $db, $tablesCloned, $prefix );
1104 $dbClone->useTemporaryTables( self::$useTemporaryTables );
1105
1106 if ( ( $db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) {
1107 CloneDatabase::changePrefix( $prefix );
1108
1109 return false;
1110 } else {
1111 $dbClone->cloneTableStructure();
1112 return true;
1113 }
1114 }
1115
1119 public function setupAllTestDBs() {
1121
1122 self::$oldTablePrefix = $wgDBprefix;
1123
1124 $testPrefix = $this->dbPrefix();
1125
1126 // switch to a temporary clone of the database
1127 self::setupTestDB( $this->db, $testPrefix );
1128
1129 if ( self::isUsingExternalStoreDB() ) {
1130 self::setupExternalStoreTestDBs( $testPrefix );
1131 }
1132 }
1133
1155 public static function setupTestDB( Database $db, $prefix ) {
1156 if ( self::$dbSetup ) {
1157 return;
1158 }
1159
1160 if ( $db->tablePrefix() === $prefix ) {
1161 throw new MWException(
1162 'Cannot run unit tests, the database prefix is already "' . $prefix . '"' );
1163 }
1164
1165 // TODO: the below should be re-written as soon as LBFactory, LoadBalancer,
1166 // and Database no longer use global state.
1167
1168 self::$dbSetup = true;
1169
1170 if ( !self::setupDatabaseWithTestPrefix( $db, $prefix ) ) {
1171 return;
1172 }
1173
1174 // Assuming this isn't needed for External Store database, and not sure if the procedure
1175 // would be available there.
1176 if ( $db->getType() == 'oracle' ) {
1177 $db->query( 'BEGIN FILL_WIKI_INFO; END;' );
1178 }
1179 }
1180
1186 protected static function setupExternalStoreTestDBs( $testPrefix ) {
1187 $connections = self::getExternalStoreDatabaseConnections();
1188 foreach ( $connections as $dbw ) {
1189 // Hack: cloneTableStructure sets $wgDBprefix to the unit test
1190 // prefix,. Even though listTables now uses tablePrefix, that
1191 // itself is populated from $wgDBprefix by default.
1192
1193 // We have to set it back, or we won't find the original 'blobs'
1194 // table to copy.
1195
1196 $dbw->tablePrefix( self::$oldTablePrefix );
1197 self::setupDatabaseWithTestPrefix( $dbw, $testPrefix );
1198 }
1199 }
1200
1208 protected static function getExternalStoreDatabaseConnections() {
1210
1212 $externalStoreDB = ExternalStore::getStoreObject( 'DB' );
1213 $defaultArray = (array)$wgDefaultExternalStore;
1214 $dbws = [];
1215 foreach ( $defaultArray as $url ) {
1216 if ( strpos( $url, 'DB://' ) === 0 ) {
1217 list( $proto, $cluster ) = explode( '://', $url, 2 );
1218 // Avoid getMaster() because setupDatabaseWithTestPrefix()
1219 // requires Database instead of plain DBConnRef/IDatabase
1220 $dbws[] = $externalStoreDB->getMaster( $cluster );
1221 }
1222 }
1223
1224 return $dbws;
1225 }
1226
1232 protected static function isUsingExternalStoreDB() {
1234 if ( !$wgDefaultExternalStore ) {
1235 return false;
1236 }
1237
1238 $defaultArray = (array)$wgDefaultExternalStore;
1239 foreach ( $defaultArray as $url ) {
1240 if ( strpos( $url, 'DB://' ) === 0 ) {
1241 return true;
1242 }
1243 }
1244
1245 return false;
1246 }
1247
1254 private function resetDB( $db, $tablesUsed ) {
1255 if ( $db ) {
1256 $userTables = [ 'user', 'user_groups', 'user_properties' ];
1257 $coreDBDataTables = array_merge( $userTables, [ 'page', 'revision' ] );
1258
1259 // If any of the user tables were marked as used, we should clear all of them.
1260 if ( array_intersect( $tablesUsed, $userTables ) ) {
1261 $tablesUsed = array_unique( array_merge( $tablesUsed, $userTables ) );
1263 }
1264
1265 $truncate = in_array( $db->getType(), [ 'oracle', 'mysql' ] );
1266 foreach ( $tablesUsed as $tbl ) {
1267 // TODO: reset interwiki table to its original content.
1268 if ( $tbl == 'interwiki' ) {
1269 continue;
1270 }
1271
1272 if ( $truncate ) {
1273 $db->query( 'TRUNCATE TABLE ' . $db->tableName( $tbl ), __METHOD__ );
1274 } else {
1275 $db->delete( $tbl, '*', __METHOD__ );
1276 }
1277
1278 if ( $tbl === 'page' ) {
1279 // Forget about the pages since they don't
1280 // exist in the DB.
1281 LinkCache::singleton()->clear();
1282 }
1283 }
1284
1285 if ( array_intersect( $tablesUsed, $coreDBDataTables ) ) {
1286 // Re-add core DB data that was deleted
1287 $this->addCoreDBData();
1288 }
1289 }
1290 }
1291
1301 public function __call( $func, $args ) {
1302 static $compatibility = [
1303 'createMock' => 'createMock2',
1304 ];
1305
1306 if ( isset( $compatibility[$func] ) ) {
1307 return call_user_func_array( [ $this, $compatibility[$func] ], $args );
1308 } else {
1309 throw new MWException( "Called non-existent $func method on " . static::class );
1310 }
1311 }
1312
1320 private function createMock2( $originalClassName ) {
1321 return $this->getMockBuilder( $originalClassName )
1322 ->disableOriginalConstructor()
1323 ->disableOriginalClone()
1324 ->disableArgumentCloning()
1325 // New in phpunit-mock-objects 3.2 (phpunit 5.4.0)
1326 // ->disallowMockingUnknownTypes()
1327 ->getMock();
1328 }
1329
1330 private static function unprefixTable( &$tableName, $ind, $prefix ) {
1331 $tableName = substr( $tableName, strlen( $prefix ) );
1332 }
1333
1334 private static function isNotUnittest( $table ) {
1335 return strpos( $table, 'unittest_' ) !== 0;
1336 }
1337
1345 public static function listTables( IMaintainableDatabase $db ) {
1346 $prefix = $db->tablePrefix();
1347 $tables = $db->listTables( $prefix, __METHOD__ );
1348
1349 if ( $db->getType() === 'mysql' ) {
1350 static $viewListCache = null;
1351 if ( $viewListCache === null ) {
1352 $viewListCache = $db->listViews( null, __METHOD__ );
1353 }
1354 // T45571: cannot clone VIEWs under MySQL
1355 $tables = array_diff( $tables, $viewListCache );
1356 }
1357 array_walk( $tables, [ __CLASS__, 'unprefixTable' ], $prefix );
1358
1359 // Don't duplicate test tables from the previous fataled run
1360 $tables = array_filter( $tables, [ __CLASS__, 'isNotUnittest' ] );
1361
1362 if ( $db->getType() == 'sqlite' ) {
1363 $tables = array_flip( $tables );
1364 // these are subtables of searchindex and don't need to be duped/dropped separately
1365 unset( $tables['searchindex_content'] );
1366 unset( $tables['searchindex_segdir'] );
1367 unset( $tables['searchindex_segments'] );
1368 $tables = array_flip( $tables );
1369 }
1370
1371 return $tables;
1372 }
1373
1378 protected function checkDbIsSupported() {
1379 if ( !in_array( $this->db->getType(), $this->supportedDBs ) ) {
1380 throw new MWException( $this->db->getType() . " is not currently supported for unit testing." );
1381 }
1382 }
1383
1389 public function getCliArg( $offset ) {
1390 if ( isset( PHPUnitMaintClass::$additionalOptions[$offset] ) ) {
1392 }
1393
1394 return null;
1395 }
1396
1402 public function setCliArg( $offset, $value ) {
1404 }
1405
1413 public function hideDeprecated( $function ) {
1414 MediaWiki\suppressWarnings();
1415 wfDeprecated( $function );
1416 MediaWiki\restoreWarnings();
1417 }
1418
1437 protected function assertSelect( $table, $fields, $condition, array $expectedRows ) {
1438 if ( !$this->needsDB() ) {
1439 throw new MWException( 'When testing database state, the test cases\'s needDB()' .
1440 ' method should return true. Use @group Database or $this->tablesUsed.' );
1441 }
1442
1443 $db = wfGetDB( DB_SLAVE );
1444
1445 $res = $db->select( $table, $fields, $condition, wfGetCaller(), [ 'ORDER BY' => $fields ] );
1446 $this->assertNotEmpty( $res, "query failed: " . $db->lastError() );
1447
1448 $i = 0;
1449
1450 foreach ( $expectedRows as $expected ) {
1451 $r = $res->fetchRow();
1452 self::stripStringKeys( $r );
1453
1454 $i += 1;
1455 $this->assertNotEmpty( $r, "row #$i missing" );
1456
1457 $this->assertEquals( $expected, $r, "row #$i mismatches" );
1458 }
1459
1460 $r = $res->fetchRow();
1461 self::stripStringKeys( $r );
1462
1463 $this->assertFalse( $r, "found extra row (after #$i)" );
1464 }
1465
1477 protected function arrayWrap( array $elements ) {
1478 return array_map(
1479 function ( $element ) {
1480 return [ $element ];
1481 },
1482 $elements
1483 );
1484 }
1485
1498 protected function assertArrayEquals( array $expected, array $actual,
1499 $ordered = false, $named = false
1500 ) {
1501 if ( !$ordered ) {
1502 $this->objectAssociativeSort( $expected );
1503 $this->objectAssociativeSort( $actual );
1504 }
1505
1506 if ( !$named ) {
1507 $expected = array_values( $expected );
1508 $actual = array_values( $actual );
1509 }
1510
1511 call_user_func_array(
1512 [ $this, 'assertEquals' ],
1513 array_merge( [ $expected, $actual ], array_slice( func_get_args(), 4 ) )
1514 );
1515 }
1516
1529 protected function assertHTMLEquals( $expected, $actual, $msg = '' ) {
1530 $expected = str_replace( '>', ">\n", $expected );
1531 $actual = str_replace( '>', ">\n", $actual );
1532
1533 $this->assertEquals( $expected, $actual, $msg );
1534 }
1535
1543 protected function objectAssociativeSort( array &$array ) {
1544 uasort(
1545 $array,
1546 function ( $a, $b ) {
1547 return serialize( $a ) > serialize( $b ) ? 1 : -1;
1548 }
1549 );
1550 }
1551
1561 protected static function stripStringKeys( &$r ) {
1562 if ( !is_array( $r ) ) {
1563 return;
1564 }
1565
1566 foreach ( $r as $k => $v ) {
1567 if ( is_string( $k ) ) {
1568 unset( $r[$k] );
1569 }
1570 }
1571 }
1572
1586 protected function assertTypeOrValue( $type, $actual, $value = false, $message = '' ) {
1587 if ( $actual === $value ) {
1588 $this->assertTrue( true, $message );
1589 } else {
1590 $this->assertType( $type, $actual, $message );
1591 }
1592 }
1593
1605 protected function assertType( $type, $actual, $message = '' ) {
1606 if ( class_exists( $type ) || interface_exists( $type ) ) {
1607 $this->assertInstanceOf( $type, $actual, $message );
1608 } else {
1609 $this->assertInternalType( $type, $actual, $message );
1610 }
1611 }
1612
1622 protected function isWikitextNS( $ns ) {
1624
1625 if ( isset( $wgNamespaceContentModels[$ns] ) ) {
1627 }
1628
1629 return true;
1630 }
1631
1639 protected function getDefaultWikitextNS() {
1641
1642 static $wikitextNS = null; // this is not going to change
1643 if ( $wikitextNS !== null ) {
1644 return $wikitextNS;
1645 }
1646
1647 // quickly short out on most common case:
1648 if ( !isset( $wgNamespaceContentModels[NS_MAIN] ) ) {
1649 return NS_MAIN;
1650 }
1651
1652 // NOTE: prefer content namespaces
1653 $namespaces = array_unique( array_merge(
1654 MWNamespace::getContentNamespaces(),
1655 [ NS_MAIN, NS_HELP, NS_PROJECT ], // prefer these
1656 MWNamespace::getValidNamespaces()
1657 ) );
1658
1659 $namespaces = array_diff( $namespaces, [
1660 NS_FILE, NS_CATEGORY, NS_MEDIAWIKI, NS_USER // don't mess with magic namespaces
1661 ] );
1662
1663 $talk = array_filter( $namespaces, function ( $ns ) {
1664 return MWNamespace::isTalk( $ns );
1665 } );
1666
1667 // prefer non-talk pages
1668 $namespaces = array_diff( $namespaces, $talk );
1669 $namespaces = array_merge( $namespaces, $talk );
1670
1671 // check default content model of each namespace
1672 foreach ( $namespaces as $ns ) {
1673 if ( !isset( $wgNamespaceContentModels[$ns] ) ||
1675 ) {
1676
1677 $wikitextNS = $ns;
1678
1679 return $wikitextNS;
1680 }
1681 }
1682
1683 // give up
1684 // @todo Inside a test, we could skip the test as incomplete.
1685 // But frequently, this is used in fixture setup.
1686 throw new MWException( "No namespace defaults to wikitext!" );
1687 }
1688
1695 protected function markTestSkippedIfNoDiff3() {
1697
1698 # This check may also protect against code injection in
1699 # case of broken installations.
1700 MediaWiki\suppressWarnings();
1701 $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
1702 MediaWiki\restoreWarnings();
1703
1704 if ( !$haveDiff3 ) {
1705 $this->markTestSkipped( "Skip test, since diff3 is not configured" );
1706 }
1707 }
1708
1717 protected function checkPHPExtension( $extName ) {
1718 $loaded = extension_loaded( $extName );
1719 if ( !$loaded ) {
1720 $this->markTestSkipped( "PHP extension '$extName' is not loaded, skipping." );
1721 }
1722
1723 return $loaded;
1724 }
1725
1740 protected function assertValidHtmlSnippet( $html ) {
1741 $html = '<!DOCTYPE html><html><head><title>test</title></head><body>' . $html . '</body></html>';
1743 }
1744
1756 protected function assertValidHtmlDocument( $html ) {
1757 // Note: we only validate if the tidy PHP extension is available.
1758 // In case wgTidyInternal is false, MWTidy would fall back to the command line version
1759 // of tidy. In that case however, we can not reliably detect whether a failing validation
1760 // is due to malformed HTML, or caused by tidy not being installed as a command line tool.
1761 // That would cause all HTML assertions to fail on a system that has no tidy installed.
1762 if ( !$GLOBALS['wgTidyInternal'] || !MWTidy::isEnabled() ) {
1763 $this->markTestSkipped( 'Tidy extension not installed' );
1764 }
1765
1766 $errorBuffer = '';
1767 MWTidy::checkErrors( $html, $errorBuffer );
1768 $allErrors = preg_split( '/[\r\n]+/', $errorBuffer );
1769
1770 // Filter Tidy warnings which aren't useful for us.
1771 // Tidy eg. often cries about parameters missing which have actually
1772 // been deprecated since HTML4, thus we should not care about them.
1773 $errors = preg_grep(
1774 '/^(.*Warning: (trimming empty|.* lacks ".*?" attribute).*|\s*)$/m',
1775 $allErrors, PREG_GREP_INVERT
1776 );
1777
1778 $this->assertEmpty( $errors, implode( "\n", $errors ) );
1779 }
1780
1785 public static function wfResetOutputBuffersBarrier( $buffer ) {
1786 return $buffer;
1787 }
1788
1796 protected function setTemporaryHook( $hookName, $handler ) {
1797 $this->mergeMwGlobalArrayValue( 'wgHooks', [ $hookName => [ $handler ] ] );
1798 }
1799
1800}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
serialize()
unserialize( $serialized)
$GLOBALS['IP']
$wgDBprefix
Table name prefix.
$wgSQLMode
SQL Mode - default is turning off all modes, including strict, if set.
array $wgDefaultExternalStore
The place to put new revisions, false to put them in the local text table.
$wgDiff3
Path to the GNU diff3 utility.
$wgJobClasses
Maps jobs to their handling classes; extensions can add to this to provide custom jobs.
$wgNamespaceContentModels
Associative array mapping namespace IDs to the name of the content model pages in that namespace shou...
const DB_SLAVE
Definition Defines.php:34
wfTempDir()
Tries to get the system directory for temporary files.
wfRecursiveRemoveDir( $dir)
Remove a directory and all its content.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfGetCaller( $level=2)
Get the name of the function which called this function wfGetCaller( 1 ) is the function with the wfG...
wfMkdirParents( $dir, $mode=null, $caller=null)
Make directory, and make all parent directories if they don't exist.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:639
if( $line===false) $args
Definition cdb.php:63
static changePrefix( $prefix)
Change the table prefix on all open DB connections/.
Factory class to create Config objects.
makeConfig( $name)
Create a given Config using the registered callback for $name.
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
static getStoreObject( $proto, array $params=[])
Get an external store object of the given type, with the given parameters.
WebRequest clone which takes values from a provided array.
static destroySingleton()
Destroy the singleton instance.
Accesses configuration settings from $GLOBALS.
A Config instance which stores all settings as a member variable.
static destroySingletons()
Destroy the singleton instances.
static singleton( $wiki=false)
Internationalisation code.
Definition Language.php:35
MediaWiki exception.
static checkErrors( $text, &$errorStr=null)
Check HTML for errors, used if $wgValidateAllHtml = true.
Definition MWTidy.php:63
static isEnabled()
Definition MWTidy.php:79
static setLBFactoryTriggers(LBFactory $LBFactory)
setupAllTestDBs()
Set up all test DBs.
int $phpErrorLevel
Original value of PHP's error_reporting setting.
getDefaultWikitextNS()
Returns the ID of a namespace that defaults to Wikitext.
static TestUser[] $users
static stripStringKeys(&$r)
Utility function for eliminating all string keys from an array.
Database $db
Primary database.
getNewTempFile()
Obtains a new temporary file name.
static isNotUnittest( $table)
static setupExternalStoreTestDBs( $testPrefix)
Clones the External Store database(s) for testing.
static getMutableTestUser( $groups=[])
Convenience method for getting a mutable test user.
insertPage( $pageName, $text='Sample page for unit test.', $namespace=null)
Insert a new page.
static getTestSysop()
Convenience method for getting an immutable admin test user.
restoreLoggers()
Restores loggers replaced by setLogger().
overrideMwServices(Config $configOverrides=null, array $services=[])
Stashes the global instance of MediaWikiServices, and installs a new one, allowing test cases to over...
static MediaWikiServices null $serviceLocator
The service locator created by prepareServices().
assertValidHtmlSnippet( $html)
Asserts that the given string is a valid HTML snippet.
setLogger( $channel, LoggerInterface $logger)
Sets the logger for a specified channel, for the duration of the test.
static getExternalStoreDatabaseConnections()
Gets master database connections for all of the ExternalStoreDB stores configured in $wgDefaultExtern...
assertType( $type, $actual, $message='')
Asserts the type of the provided value.
run(PHPUnit_Framework_TestResult $result=null)
static resetGlobalServices(Config $bootstrapConfig=null)
Reset global services, and install testing environment.
mergeMwGlobalArrayValue( $name, $values)
Merges the given values into a MW global array variable.
doLightweightServiceReset()
Resets some well known services that typically have state that may interfere with unit tests.
static setupTestDB(Database $db, $prefix)
Creates an empty skeleton of the wiki database by cloning its structure to equivalent tables using th...
static canShallowCopy( $value)
Check if we can back up a value by performing a shallow copy.
setCliArg( $offset, $value)
setMwGlobals( $pairs, $value=null)
testMediaWikiTestCaseParentSetupCalled()
Make sure MediaWikiTestCase extending classes have called their parent setUp method.
array $mwGlobals
Holds original values of MediaWiki configuration settings to be restored in tearDown().
static installTestServices(ConfigFactory $oldConfigFactory, MediaWikiServices $newServices)
array $tmpFiles
Holds the paths of temporary files/directories created through getNewTempFile, and getNewTempDirector...
assertValidHtmlDocument( $html)
Asserts that the given string is valid HTML document.
hideDeprecated( $function)
Don't throw a warning if $function is deprecated and called later.
static setupDatabaseWithTestPrefix(IMaintainableDatabase $db, $prefix)
Setups a database with the given prefix.
__construct( $name=null, array $data=[], $dataName='')
checkPHPExtension( $extName)
Check if $extName is a loaded PHP extension, will skip the test whenever it is not loaded.
static listTables(IMaintainableDatabase $db)
resetDB( $db, $tablesUsed)
Empty all tables so they can be repopulated for tests.
static makeTestConfig(Config $baseConfig=null, Config $customOverrides=null)
Create a config suitable for testing, based on a base config, default overrides, and custom overrides...
static teardownTestDB()
Restores MediaWiki to using the table set (table prefix) it was using before setupTestDB() was called...
static wfResetOutputBuffersBarrier( $buffer)
Used as a marker to prevent wfResetOutputBuffers from breaking PHPUnit.
$called
$called tracks whether the setUp and tearDown method has been called.
createMock2( $originalClassName)
Return a test double for the specified class.
setService( $name, $object)
Sets a service, maintaining a stashed version of the previous service to be restored in tearDown.
const DB_PREFIX
Table name prefixes.
static getTestUser( $groups=[])
Convenience method for getting an immutable test user.
static makeTestConfigFactoryInstantiator(ConfigFactory $oldFactory, array $configurations)
isWikitextNS( $ns)
Returns true if the given namespace defaults to Wikitext according to $wgNamespaceContentModels.
getNewTempDirectory()
obtains a new temporary directory
static isUsingExternalStoreDB()
Check whether ExternalStoreDB is being used.
array $mwGlobalsToUnset
Holds list of MediaWiki configuration settings to be unset in tearDown().
objectAssociativeSort(array &$array)
Does an associative sort that works for objects.
setTemporaryHook( $hookName, $handler)
Create a temporary hook handler which will be reset by tearDown.
LoggerInterface[] $loggers
Holds original loggers which have been replaced by setLogger()
static prepareServices(Config $bootstrapConfig)
Prepare service configuration for unit testing.
stashMwGlobals( $globalKeys)
Stashes the global, will be restored in tearDown()
static unprefixTable(&$tableName, $ind, $prefix)
assertHTMLEquals( $expected, $actual, $msg='')
Put each HTML element on its own line and then equals() the results.
assertSelect( $table, $fields, $condition, array $expectedRows)
Asserts that the given database query yields the rows given by $expectedRows.
arrayWrap(array $elements)
Utility method taking an array of elements and wrapping each element in its own array.
assertArrayEquals(array $expected, array $actual, $ordered=false, $named=false)
Assert that two arrays are equal.
markTestSkippedIfNoDiff3()
Check, if $wgDiff3 is set and ready to merge Will mark the calling test as skipped,...
assertTypeOrValue( $type, $actual, $value=false, $message='')
Asserts that the provided variable is of the specified internal type or equals the $value argument.
LoggerFactory service provider that creates LegacyLogger instances.
Definition LegacySpi.php:38
PSR-3 logger instance factory.
LoggerFactory service provider that creates loggers implemented by Monolog.
MediaWikiServices is the service locator for the application scope of MediaWiki.
getBootstrapConfig()
Returns the Config object containing the bootstrap configuration.
resetServiceForTesting( $name, $destroy=true)
Resets the given service for testing purposes.
redefineService( $name, callable $instantiator)
Replace an already defined service.
Provides a fallback sequence for Config objects.
static $additionalOptions
Definition phpunit.php:18
static resetMain()
Resets singleton returned by getMain().
static getMain()
Static methods.
static clear()
Clear the registry.
static getMutableTestUser( $testName, $groups=[])
Get a TestUser object that the caller may modify.
static getImmutableTestUser( $groups=[])
Get a TestUser object that the caller may not modify.
Wraps the user object, so we can also retain full access to properties like password if we log in via...
Definition TestUser.php:7
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:50
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition WikiPage.php:120
Content object for wiki text pages.
$res
Definition database.txt:21
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
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
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
$lbFactory
const NS_HELP
Definition Defines.php:74
const NS_USER
Definition Defines.php:64
const NS_FILE
Definition Defines.php:68
const CACHE_NONE
Definition Defines.php:100
const NS_MAIN
Definition Defines.php:62
const NS_MEDIAWIKI
Definition Defines.php:70
const CACHE_ACCEL
Definition Defines.php:103
const CONTENT_MODEL_WIKITEXT
Definition Defines.php:233
const CACHE_MEMCACHED
Definition Defines.php:102
const CACHE_DB
Definition Defines.php:101
const NS_PROJECT
Definition Defines.php:66
const NS_CATEGORY
Definition Defines.php:76
const EDIT_NEW
Definition Defines.php:150
the array() calling protocol came about after MediaWiki 1.4rc1.
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition hooks.txt:249
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:2578
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
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:1018
namespace and then decline to actually register it & $namespaces
Definition hooks.txt:934
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:964
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 my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition hooks.txt:2604
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:2224
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
Definition hooks.txt:1974
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
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
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
Definition hooks.txt:903
processing should stop and the error should be shown to the user * false
Definition hooks.txt:189
returning false will NOT prevent logging $e
Definition hooks.txt:2127
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:37
$buffer
const DB_MASTER
Definition defines.php:26
if(!isset( $args[0])) $lang