Support redirects in JavaScriptContent
When a JavaScript page is moved, a "redirect" in the form of mw.loader.load(...) will be left behind, so any other JavaScript loading the page that way will still work, albeit with an extra HTTP request. This also implements Content::getRedirectTarget(), so redirects are marked properly in the database, and users viewing them are redirected properly. A magic "/* #REDIRECT */" comment must be in front of the mw.loader.load call. This is done so that pages which currently are just one mw.loader.load call aren't turned into redirects. Bug: 71200 Bug: 33973 Change-Id: I10fdff087a901da56fad64531f0e382f90ebcf37
This commit is contained in:
parent
06c7063501
commit
ad9f14d662
7 changed files with 170 additions and 10 deletions
|
|
@ -363,9 +363,8 @@ class MediaWiki {
|
||||||
$this->context->setWikiPage( $article->getPage() );
|
$this->context->setWikiPage( $article->getPage() );
|
||||||
}
|
}
|
||||||
|
|
||||||
// NS_MEDIAWIKI has no redirects.
|
// Skip some unnecessary code if the content model doesn't support redirects
|
||||||
// It is also used for CSS/JS, so performance matters here...
|
if ( !ContentHandler::getForTitle( $title )->supportsRedirects() ) {
|
||||||
if ( $title->getNamespace() == NS_MEDIAWIKI ) {
|
|
||||||
return $article;
|
return $article;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,11 @@
|
||||||
*/
|
*/
|
||||||
class JavaScriptContent extends TextContent {
|
class JavaScriptContent extends TextContent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool|Title|null
|
||||||
|
*/
|
||||||
|
private $redirectTarget = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $text JavaScript code.
|
* @param string $text JavaScript code.
|
||||||
* @param string $modelId the content model name
|
* @param string $modelId the content model name
|
||||||
|
|
@ -73,4 +78,46 @@ class JavaScriptContent extends TextContent {
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this page is a redirect, return the content
|
||||||
|
* if it should redirect to $target instead
|
||||||
|
*
|
||||||
|
* @param Title $target
|
||||||
|
* @return JavaScriptContent
|
||||||
|
*/
|
||||||
|
public function updateRedirect( Title $target ) {
|
||||||
|
if ( !$this->isRedirect() ) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getContentHandler()->makeRedirectContent( $target );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Title|null
|
||||||
|
*/
|
||||||
|
public function getRedirectTarget() {
|
||||||
|
if ( $this->redirectTarget !== false ) {
|
||||||
|
return $this->redirectTarget;
|
||||||
|
}
|
||||||
|
$this->redirectTarget = null;
|
||||||
|
$text = $this->getNativeData();
|
||||||
|
if ( strpos( $text, '/* #REDIRECT */' ) === 0 ) {
|
||||||
|
// Extract the title from the url
|
||||||
|
preg_match( '/title=(.*?)\\\\u0026action=raw/', $text, $matches );
|
||||||
|
if ( isset( $matches[1] ) ) {
|
||||||
|
$title = Title::newFromText( $matches[1] );
|
||||||
|
if ( $title ) {
|
||||||
|
// Have a title, check that the current content equals what
|
||||||
|
// the redirect content should be
|
||||||
|
if ( $this->equals( $this->getContentHandler()->makeRedirectContent( $title ) ) ) {
|
||||||
|
$this->redirectTarget = $title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectTarget;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,4 +41,22 @@ class JavaScriptContentHandler extends CodeContentHandler {
|
||||||
protected function getContentClass() {
|
protected function getContentClass() {
|
||||||
return 'JavaScriptContent';
|
return 'JavaScriptContent';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function supportsRedirects() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a redirect that is also valid JavaScript
|
||||||
|
*
|
||||||
|
* @param Title $destination
|
||||||
|
* @param string $text ignored
|
||||||
|
* @return JavaScriptContent
|
||||||
|
*/
|
||||||
|
public function makeRedirectContent( Title $destination, $text = '' ) {
|
||||||
|
// The parameters are passed as a string so the / is not url-encoded by wfArrayToCgi
|
||||||
|
$url = $destination->getFullURL( 'action=raw&ctype=text/javascript', false, PROTO_RELATIVE );
|
||||||
|
$class = $this->getContentClass();
|
||||||
|
return new $class( '/* #REDIRECT */' . Xml::encodeJsCall( 'mw.loader.load', array( $url ) ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
|
||||||
*/
|
*/
|
||||||
protected function getContent( $titleText ) {
|
protected function getContent( $titleText ) {
|
||||||
$title = Title::newFromText( $titleText );
|
$title = Title::newFromText( $titleText );
|
||||||
if ( !$title || $title->isRedirect() ) {
|
if ( !$title ) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
* @group ContentHandler
|
* @group ContentHandler
|
||||||
* @group Database
|
* @group Database
|
||||||
* ^--- needed, because we do need the database to test link updates
|
* ^--- needed, because we do need the database to test link updates
|
||||||
|
*
|
||||||
|
* @FIXME this should not extend JavaScriptContentTest.
|
||||||
*/
|
*/
|
||||||
class CssContentTest extends JavaScriptContentTest {
|
class CssContentTest extends JavaScriptContentTest {
|
||||||
|
|
||||||
|
|
@ -68,6 +70,27 @@ class CssContentTest extends JavaScriptContentTest {
|
||||||
$this->assertEquals( CONTENT_MODEL_CSS, $content->getContentHandler()->getModelID() );
|
$this->assertEquals( CONTENT_MODEL_CSS, $content->getContentHandler()->getModelID() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirects aren't supported
|
||||||
|
*/
|
||||||
|
public static function provideUpdateRedirect() {
|
||||||
|
return array(
|
||||||
|
array(
|
||||||
|
'#REDIRECT [[Someplace]]',
|
||||||
|
'#REDIRECT [[Someplace]]',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override this since CssContent does not support redirects yet
|
||||||
|
*/
|
||||||
|
public static function provideGetRedirectTarget() {
|
||||||
|
return array(
|
||||||
|
array( null, '' ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public static function dataEquals() {
|
public static function dataEquals() {
|
||||||
return array(
|
return array(
|
||||||
array( new CssContent( 'hallo' ), null, false ),
|
array( new CssContent( 'hallo' ), null, false ),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class JavaScriptContentHandlerTest extends MediaWikiTestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideMakeRedirectContent
|
||||||
|
* @covers JavaScriptContentHandler::makeRedirectContent
|
||||||
|
*/
|
||||||
|
public function testMakeRedirectContent( $title, $expected ) {
|
||||||
|
$this->setMwGlobals( array(
|
||||||
|
'wgServer' => '//example.org',
|
||||||
|
'wgScript' => '/w/index.php',
|
||||||
|
) );
|
||||||
|
$ch = new JavaScriptContentHandler();
|
||||||
|
$content = $ch->makeRedirectContent( Title::newFromText( $title ) );
|
||||||
|
$this->assertInstanceOf( 'JavaScriptContent', $content );
|
||||||
|
$this->assertEquals( $expected, $content->serialize( CONTENT_FORMAT_JAVASCRIPT ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep this in sync with JavaScriptContentTest::provideGetRedirectTarget()
|
||||||
|
*/
|
||||||
|
public static function provideMakeRedirectContent() {
|
||||||
|
return array(
|
||||||
|
array( 'MediaWiki:MonoBook.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=MediaWiki:MonoBook.js\u0026action=raw\u0026ctype=text/javascript");' ),
|
||||||
|
array( 'User:FooBar/common.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=User:FooBar/common.js\u0026action=raw\u0026ctype=text/javascript");' ),
|
||||||
|
array( 'Gadget:FooBaz.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=Gadget:FooBaz.js\u0026action=raw\u0026ctype=text/javascript");' ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -251,16 +251,31 @@ class JavaScriptContentTest extends TextContentTest {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers JavaScriptContent::updateRedirect
|
* @covers JavaScriptContent::updateRedirect
|
||||||
|
* @dataProvider provideUpdateRedirect
|
||||||
*/
|
*/
|
||||||
public function testUpdateRedirect() {
|
public function testUpdateRedirect( $oldText, $expectedText) {
|
||||||
|
$this->setMwGlobals( array(
|
||||||
|
'wgServer' => '//example.org',
|
||||||
|
'wgScriptPath' => '/w/index.php',
|
||||||
|
) );
|
||||||
$target = Title::newFromText( "testUpdateRedirect_target" );
|
$target = Title::newFromText( "testUpdateRedirect_target" );
|
||||||
|
|
||||||
$content = $this->newContent( "#REDIRECT [[Someplace]]" );
|
$content = new JavaScriptContent( $oldText );
|
||||||
$newContent = $content->updateRedirect( $target );
|
$newContent = $content->updateRedirect( $target );
|
||||||
|
|
||||||
$this->assertTrue(
|
$this->assertEquals( $expectedText, $newContent->getNativeData() );
|
||||||
$content->equals( $newContent ),
|
}
|
||||||
"content should be unchanged since it's not wikitext"
|
|
||||||
|
public static function provideUpdateRedirect() {
|
||||||
|
return array(
|
||||||
|
array(
|
||||||
|
'#REDIRECT [[Someplace]]',
|
||||||
|
'#REDIRECT [[Someplace]]',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=MediaWiki:MonoBook.js\u0026action=raw\u0026ctype=text/javascript");',
|
||||||
|
'/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=TestUpdateRedirect_target\u0026action=raw\u0026ctype=text/javascript");'
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -290,4 +305,32 @@ class JavaScriptContentTest extends TextContentTest {
|
||||||
array( new JavaScriptContent( "hallo" ), new JavaScriptContent( "HALLO" ), false ),
|
array( new JavaScriptContent( "hallo" ), new JavaScriptContent( "HALLO" ), false ),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideGetRedirectTarget
|
||||||
|
*/
|
||||||
|
public function testGetRedirectTarget( $title, $text ) {
|
||||||
|
$this->setMwGlobals( array(
|
||||||
|
'wgServer' => '//example.org',
|
||||||
|
'wgScriptPath' => '/w/index.php',
|
||||||
|
) );
|
||||||
|
$content = new JavaScriptContent( $text );
|
||||||
|
$target = $content->getRedirectTarget();
|
||||||
|
$this->assertEquals( $title, $target ? $target->getPrefixedText() : null );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep this in sync with JavaScriptContentHandlerTest::provideMakeRedirectContent()
|
||||||
|
*/
|
||||||
|
public static function provideGetRedirectTarget() {
|
||||||
|
return array(
|
||||||
|
array( 'MediaWiki:MonoBook.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=MediaWiki:MonoBook.js\u0026action=raw\u0026ctype=text/javascript");' ),
|
||||||
|
array( 'User:FooBar/common.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=User:FooBar/common.js\u0026action=raw\u0026ctype=text/javascript");' ),
|
||||||
|
array( 'Gadget:FooBaz.js', '/* #REDIRECT */mw.loader.load("//example.org/w/index.php?title=Gadget:FooBaz.js\u0026action=raw\u0026ctype=text/javascript");' ),
|
||||||
|
// No #REDIRECT comment
|
||||||
|
array( null, 'mw.loader.load("//example.org/w/index.php?title=MediaWiki:NoRedirect.js\u0026action=raw\u0026ctype=text/javascript");' ),
|
||||||
|
// Different domain
|
||||||
|
array( null, '/* #REDIRECT */mw.loader.load("//example.com/w/index.php?title=MediaWiki:OtherWiki.js\u0026action=raw\u0026ctype=text/javascript");' ),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue