creations, similar to the topOnly option.
* Add mediawiki.ui.button styling to all pages so wiki content can use styled
buttons.
-* Special:UserLogin/signup now does AJAX checks for invalid and taken usernames,
- displaying the error live.
-* Special:UserLogin/signup now warns the user if their chosen username has to be
- normalized.
=== Bug fixes in 1.23 ===
* (bug 41759) The "updated since last visit" markers (on history pages, recent
* @param bool $refreshCache If set, refreshes the diff cache
* @param bool $unhide If set, allow viewing deleted revs
*/
- function __construct( $context = null, $old = 0, $new = 0, $rcid = 0,
- $refreshCache = false, $unhide = false ) {
+ public function __construct( $context = null, $old = 0, $new = 0, $rcid = 0,
+ $refreshCache = false, $unhide = false
+ ) {
if ( $context instanceof IContextSource ) {
$this->setContext( $context );
}
/**
* @param bool $value
*/
- function setReducedLineNumbers( $value = true ) {
+ public function setReducedLineNumbers( $value = true ) {
$this->mReducedLineNumbers = $value;
}
/**
* @return Language
*/
- function getDiffLang() {
+ public function getDiffLang() {
if ( $this->mDiffLang === null ) {
# Default language in which the diff text is written.
$this->mDiffLang = $this->getTitle()->getPageLanguage();
/**
* @return bool
*/
- function wasCacheHit() {
+ public function wasCacheHit() {
return $this->mCacheHit;
}
/**
* @return int
*/
- function getOldid() {
+ public function getOldid() {
$this->loadRevisionIds();
return $this->mOldid;
/**
* @return bool|int
*/
- function getNewid() {
+ public function getNewid() {
$this->loadRevisionIds();
return $this->mNewid;
*
* @return mixed URL or false
*/
- function deletedLink( $id ) {
+ public function deletedLink( $id ) {
if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow( 'archive', '*',
*
* @return string Wikitext fragment
*/
- function deletedIdMarker( $id ) {
+ public function deletedIdMarker( $id ) {
$link = $this->deletedLink( $id );
if ( $link ) {
return "[$link $id]";
$this->getLanguage()->listToText( $missing ), count( $missing ) );
}
- function showDiffPage( $diffOnly = false ) {
+ public function showDiffPage( $diffOnly = false ) {
wfProfileIn( __METHOD__ );
# Allow frames except in certain special cases
/**
* Show the new revision of the page.
*/
- function renderNewRevision() {
+ public function renderNewRevision() {
wfProfileIn( __METHOD__ );
$out = $this->getOutput();
$revHeader = $this->getRevisionHeader( $this->mNewRev );
*
* @return bool
*/
- function showDiff( $otitle, $ntitle, $notice = '' ) {
+ public function showDiff( $otitle, $ntitle, $notice = '' ) {
$diff = $this->getDiff( $otitle, $ntitle, $notice );
if ( $diff === false ) {
$this->showMissingRevision();
/**
* Add style sheets and supporting JS for diff display.
*/
- function showDiffStyle() {
+ public function showDiffStyle() {
$this->getOutput()->addModuleStyles( 'mediawiki.action.history.diff' );
}
*
* @return mixed
*/
- function getDiff( $otitle, $ntitle, $notice = '' ) {
+ public function getDiff( $otitle, $ntitle, $notice = '' ) {
$body = $this->getDiffBody();
if ( $body === false ) {
return false;
* @throws MWException If old or new content is not an instance of TextContent.
* @return bool|string
*/
- function generateContentDiffBody( Content $old, Content $new ) {
+ public function generateContentDiffBody( Content $old, Content $new ) {
if ( !( $old instanceof TextContent ) ) {
throw new MWException( "Diff not implemented for " . get_class( $old ) . "; " .
"override generateContentDiffBody to fix this." );
* @return bool|string
* @deprecated since 1.21, use generateContentDiffBody() instead!
*/
- function generateDiffBody( $otext, $ntext ) {
+ public function generateDiffBody( $otext, $ntext ) {
ContentHandler::deprecated( __METHOD__, "1.21" );
return $this->generateTextDiffBody( $otext, $ntext );
*
* @return bool|string
*/
- function generateTextDiffBody( $otext, $ntext ) {
+ public function generateTextDiffBody( $otext, $ntext ) {
global $wgExternalDiffEngine, $wgContLang;
wfProfileIn( __METHOD__ );
*
* @return mixed
*/
- function localiseLineNumbers( $text ) {
+ public function localiseLineNumbers( $text ) {
return preg_replace_callback(
'/<!--LINE (\d+)-->/',
array( &$this, 'localiseLineNumbersCb' ),
);
}
- function localiseLineNumbersCb( $matches ) {
+ public function localiseLineNumbersCb( $matches ) {
if ( $matches[1] === '1' && $this->mReducedLineNumbers ) {
return '';
}
*
* @return string
*/
- function getMultiNotice() {
+ public function getMultiNotice() {
if ( !is_object( $this->mOldRev ) || !is_object( $this->mNewRev ) ) {
return '';
} elseif ( !$this->mOldPage->equals( $this->mNewPage ) ) {
*
* @return string
*/
- function addHeader( $diff, $otitle, $ntitle, $multi = '', $notice = '' ) {
+ public function addHeader( $diff, $otitle, $ntitle, $multi = '', $notice = '' ) {
// shared.css sets diff in interface language/dir, but the actual content
// is often in a different language, mostly the page content language/dir
$tableClass = 'diff diff-contentalign-' . htmlspecialchars( $this->getDiffLang()->alignStart() );
* Use specified text instead of loading from the database
* @deprecated since 1.21, use setContent() instead.
*/
- function setText( $oldText, $newText ) {
+ public function setText( $oldText, $newText ) {
ContentHandler::deprecated( __METHOD__, "1.21" );
$oldContent = ContentHandler::makeContent( $oldText, $this->getTitle() );
* Use specified text instead of loading from the database
* @since 1.21
*/
- function setContent( Content $oldContent, Content $newContent ) {
+ public function setContent( Content $oldContent, Content $newContent ) {
$this->mOldContent = $oldContent;
$this->mNewContent = $newContent;
* (Defaults to page content language).
* @since 1.19
*/
- function setTextLanguage( $lang ) {
+ public function setTextLanguage( $lang ) {
$this->mDiffLang = wfGetLangObj( $lang );
}
*
* @return bool
*/
- function loadRevisionData() {
+ public function loadRevisionData() {
if ( $this->mRevisionsLoaded ) {
return true;
}
*
* @return bool
*/
- function loadText() {
+ public function loadText() {
if ( $this->mTextLoaded == 2 ) {
return true;
}
*
* @return bool
*/
- function loadNewText() {
+ public function loadNewText() {
if ( $this->mTextLoaded >= 1 ) {
return true;
}
array(),
array( 'page' => $userpage->getPrefixedText() )
);
+
+ # Suppression log link (bug 59120)
+ if ( $this->getUser()->isAllowed( 'suppressionlog' ) ) {
+ $tools[] = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Log', 'suppress' ),
+ $this->msg( 'sp-contributions-suppresslog' )->escaped(),
+ array(),
+ array( 'offender' => $username )
+ );
+ }
}
# Uploads
$tools[] = Linker::linkKnown(
'page' => $nt->getPrefixedText()
)
);
+ # Suppression log link (bug 59120)
+ if ( $this->getUser()->isAllowed( 'suppressionlog' ) ) {
+ $tools[] = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Log', 'suppress' ),
+ $this->msg( 'sp-contributions-suppresslog' )->escaped(),
+ array(),
+ array( 'offender' => $userObj->getName() )
+ );
+ }
}
# Uploads
$status = $this->addNewAccountInternal();
if ( !$status->isGood() ) {
$error = $status->getMessage();
- $this->mainLoginForm( $error->toString(), $status->isOK() ? 'warning' : 'error' );
+ $this->mainLoginForm( $error->toString() );
return;
}
$status = $this->addNewAccountInternal();
if ( !$status->isGood() ) {
$error = $status->getMessage();
- $this->mainLoginForm( $error->toString(), $status->isOK() ? 'warning' : 'error' );
+ $this->mainLoginForm( $error->toString() );
return false;
}
return Status::newFatal( 'sorbs_create_account_reason' );
}
- // Leading/trailing/multiple whitespace characters are never accepted in usernames and users
- // know that, don't warn if someone accidentally types it. We do warn about underscores.
- $name = trim( preg_replace( '/\s+/', ' ', $this->mUsername ) );
-
// Normalize the name so that silly things don't cause "invalid username" errors.
// User::newFromName does some rather strict checking, rejecting e.g. leading/trailing/multiple spaces.
$title = Title::makeTitleSafe( NS_USER, $this->mUsername );
return Status::newFatal( 'noname' );
}
- // Now create a dummy user ($u) and check if it is valid.
+ # Now create a dummy user ($u) and check if it is valid
$u = User::newFromName( $title->getText(), 'creatable' );
-
if ( !is_object( $u ) ) {
return Status::newFatal( 'noname' );
} elseif ( 0 != $u->idForName() ) {
return Status::newFatal( 'userexists' );
- } elseif ( $name !== $u->getName() ) {
- // User name was adjusted due to technical restrictions (e.g. first letter capitalized).
- // This is normally handled by a client-side check, but users with JavaScript disabled get here.
- $status = Status::newGood();
- $status->warning( 'createacct-normalization', $name, $u->getName() );
-
- // Set the form field to the correct name, so the user can just hit the button again.
- $this->mUsername = $u->getName();
-
- return $status;
}
if ( $this->mCreateaccountMail ) {
<section class="mw-form-header">
<?php $this->html( 'header' ); /* extensions such as ConfirmEdit add form HTML here */ ?>
</section>
- <!-- This element is used by the mediawiki.special.userlogin.signup.js module. -->
- <div
- id="mw-createacct-status-area"
- <?php if ( $this->data['message'] ) { ?>
- class="<?php echo $this->data['messagetype']; ?>box"
- <?php } else { ?>
- style="display: none;"
- <?php } ?>
- >
<?php if ( $this->data['message'] ) { ?>
+ <div class="<?php $this->text( 'messagetype' ); ?>box">
<?php if ( $this->data['messagetype'] == 'error' ) { ?>
<strong><?php $this->msg( 'createacct-error' ); ?></strong>
<br />
<?php } ?>
<?php $this->html( 'message' ); ?>
+ </div>
<?php } ?>
- </div>
<div>
<label for='wpName2'>
'badretype' => 'The passwords you entered do not match.',
'userexists' => 'Username entered already in use.
Please choose a different name.',
-'createacct-normalization' => 'Your username will be adjusted to "$2" due to technical restrictions.',
'loginerror' => 'Login error',
'createacct-error' => 'Account creation error',
'createaccounterror' => 'Could not create account: $1',
'sp-contributions-newbies-sub' => 'For new accounts',
'sp-contributions-newbies-title' => 'User contributions for new accounts',
'sp-contributions-blocklog' => 'block log',
+'sp-contributions-suppresslog' => 'suppressed user contributions',
'sp-contributions-deleted' => 'deleted user contributions',
'sp-contributions-uploads' => 'uploads',
'sp-contributions-logs' => 'logs',
* $1 - number of contributors (users)',
'badretype' => 'Used as error message when the new password and its retype do not match.',
'userexists' => 'Used as error message in creating a user account.',
-'createacct-normalization' => 'Used as warning message on account creation when user name is adjusted silently due to technical restrictions (e.g. first letter capitalized, underscores converted to spaces).
-* $1 - the old username
-* $2 - the new username',
'loginerror' => 'Used as title of error message.
{{Identical|Login error}}',
'createacct-error' => 'Used as heading for the error message.',
* {{msg-mw|Sp-contributions-deleted}}
* {{msg-mw|Sp-contributions-userrights}}
{{Identical|Block log}}',
+'sp-contributions-suppresslog' => 'Used as a display name for a link to log entries of suppressed edits made by that user.
+
+Used as link title in [[Special:Contributions]] and in [[Special:DeletedContributions]].',
'sp-contributions-deleted' => "This is a link anchor used in [[Special:Contributions]]/''name'', when user viewing the page has the right to delete pages, or to restore deleted pages.
Used as link title in [[Special:Contributions]].
'createacct-benefit-body3',
'badretype',
'userexists',
- 'createacct-normalization',
'loginerror',
'createacct-error',
'createaccounterror',
'sp-contributions-blocked-notice',
'sp-contributions-blocked-notice-anon',
'sp-contributions-search',
+ 'sp-contributions-suppresslog',
'sp-contributions-username',
'sp-contributions-toponly',
'sp-contributions-newonly',
'mediawiki.special.userlogin.signup.js' => array(
'scripts' => 'resources/mediawiki.special/mediawiki.special.userlogin.signup.js',
'messages' => array(
- 'createacct-error',
'createacct-emailrequired',
- 'createacct-normalization',
- 'noname',
- 'userexists',
- ),
- 'dependencies' => array(
- 'mediawiki.api',
- 'mediawiki.jqueryMsg',
- 'jquery.throttle-debounce',
),
+ 'dependencies' => 'mediawiki.jqueryMsg',
),
'mediawiki.special.javaScriptTest' => array(
'scripts' => 'resources/mediawiki.special/mediawiki.special.javaScriptTest.js',
*/
( function ( mw, $ ) {
// When sending password by email, hide the password input fields.
- $( function () {
+ function hidePasswordOnEmail() {
// Always required if checked, otherwise it depends, so we use the original
var $emailLabel = $( 'label[for="wpEmail"]' ),
originalText = $emailLabel.text(),
$createByMailCheckbox.on( 'change', updateForCheckbox );
updateForCheckbox();
- } );
+ }
- // Show username normalisation warning
- $( function () {
- var
- // All of these are apparently required to be sure we detect any changes.
- events = 'keyup keydown change mouseup cut paste focus blur',
- $input = $( '#wpName2' ),
- $warningContainer = $( '#mw-createacct-status-area' ),
- api = new mw.Api(),
- currentRequest,
- tweakedUsername;
-
- // Hide any warnings / errors.
- function cleanup() {
- $warningContainer.slideUp( function () {
- $warningContainer
- .removeAttr( 'class' )
- .empty();
- } );
- }
-
- function updateUsernameStatus() {
- var
- // Leading/trailing/multiple whitespace characters are never accepted in usernames and users
- // know that, don't warn if someone accidentally types it. We do warn about underscores.
- username = $.trim( $input.val().replace( /\s+/g, ' ' ) ),
- currentRequestInternal;
-
- // Abort any pending requests.
- if ( currentRequest ) {
- currentRequest.abort();
- }
-
- if ( username === '' ) {
- cleanup();
- return;
- }
-
- currentRequest = currentRequestInternal = api.get( {
- action: 'query',
- list: 'users',
- ususers: username // '|' in usernames is handled below
- } ).done( function ( resp ) {
- var userinfo, state;
-
- // Another request was fired in the meantime, the result we got here is no longer current.
- // This shouldn't happen as we abort pending requests, but you never know.
- if ( currentRequest !== currentRequestInternal ) {
- return;
- }
-
- tweakedUsername = undefined;
-
- userinfo = resp.query.users[0];
-
- if ( resp.query.users.length !== 1 ) {
- // Happens if the user types '|' into the field
- state = 'invalid';
- } else if ( userinfo.invalid !== undefined ) {
- state = 'invalid';
- } else if ( userinfo.userid !== undefined ) {
- state = 'taken';
- } else if ( username !== userinfo.name ) {
- state = 'tweaked';
- } else {
- state = 'ok';
- }
-
- if ( state === 'ok' ) {
- cleanup();
- } else if ( state === 'tweaked' ) {
- $warningContainer
- .attr( 'class', 'warningbox' )
- .text( mw.message( 'createacct-normalization', username, userinfo.name ).text() )
- .slideDown();
-
- tweakedUsername = userinfo.name;
- } else {
- $warningContainer
- .attr( 'class', 'errorbox' )
- .empty()
- .append(
- $( '<strong>' ).text( mw.message( 'createacct-error' ).text() ),
- $( '<br>' ) // Ugh
- );
-
- if ( state === 'invalid' ) {
- $warningContainer
- .attr( 'class', 'errorbox' )
- .append( document.createTextNode( mw.message( 'noname' ).text() ) )
- .slideDown();
- } else if ( state === 'taken' ) {
- $warningContainer
- .attr( 'class', 'errorbox' )
- .append( document.createTextNode( mw.message( 'userexists' ).text() ) )
- .slideDown();
- }
-
- $warningContainer.slideDown();
- }
- } ).fail( function () {
- cleanup();
- } );
- }
-
- $input.on( events, $.debounce( 250, updateUsernameStatus ) );
-
- $input.closest( 'form' ).on( 'submit', function () {
- // If the username has to be adjusted before it's accepted, server-side check will force the
- // form to be resubmitted. Let's prevent that.
- if ( tweakedUsername !== undefined ) {
- $input.val( tweakedUsername );
- }
- } );
- } );
+ $( hidePasswordOnEmail );
}( mediaWiki, jQuery ) );
}
/**
- * FIXME: This should not need database, but Language#formatExpiry does (bug 55912)
- * @group Database
- * @todo this should be split up into multiple test methods
* @covers Message::numParams
- * @covers Message::durationParams
- * @covers Message::expiryParams
- * @covers Message::timeperiodParams
- * @covers Message::sizeParams
- * @covers Message::bitrateParams
*/
- public function testMessageParamTypes() {
+ public function testMessageNumParams() {
$lang = Language::factory( 'en' );
-
$msg = new RawMessage( '$1' );
+
$this->assertEquals(
$lang->formatNum( 123456.789 ),
$msg->inLanguage( $lang )->numParams( 123456.789 )->plain(),
'numParams is handled correctly'
);
+ }
+ /**
+ * @covers Message::durationParams
+ */
+ public function testMessageDurationParams() {
+ $lang = Language::factory( 'en' );
$msg = new RawMessage( '$1' );
+
$this->assertEquals(
$lang->formatDuration( 1234 ),
$msg->inLanguage( $lang )->durationParams( 1234 )->plain(),
'durationParams is handled correctly'
);
+ }
+ /**
+ * FIXME: This should not need database, but Language#formatExpiry does (bug 55912)
+ * @group Database
+ * @covers Message::expiryParams
+ */
+ public function testMessageExpiryParams() {
+ $lang = Language::factory( 'en' );
$msg = new RawMessage( '$1' );
+
$this->assertEquals(
$lang->formatExpiry( wfTimestampNow() ),
$msg->inLanguage( $lang )->expiryParams( wfTimestampNow() )->plain(),
'expiryParams is handled correctly'
);
+ }
+ /**
+ * @covers Message::timeperiodParams
+ */
+ public function testMessageTimeperiodParams() {
+ $lang = Language::factory( 'en' );
$msg = new RawMessage( '$1' );
+
$this->assertEquals(
$lang->formatTimePeriod( 1234 ),
$msg->inLanguage( $lang )->timeperiodParams( 1234 )->plain(),
'timeperiodParams is handled correctly'
);
+ }
+ /**
+ * @covers Message::sizeParams
+ */
+ public function testMessageSizeParams() {
+ $lang = Language::factory( 'en' );
$msg = new RawMessage( '$1' );
+
$this->assertEquals(
$lang->formatSize( 123456 ),
$msg->inLanguage( $lang )->sizeParams( 123456 )->plain(),
'sizeParams is handled correctly'
);
+ }
+ /**
+ * @covers Message::bitrateParams
+ */
+ public function testMessageBitrateParams() {
+ $lang = Language::factory( 'en' );
$msg = new RawMessage( '$1' );
+
$this->assertEquals(
$lang->formatBitrate( 123456 ),
$msg->inLanguage( $lang )->bitrateParams( 123456 )->plain(),
--- /dev/null
+<?php
+
+/**
+ * Verifies that special page aliases are valid, with no slashes.
+ *
+ * @group Language
+ * @group SpecialPageAliases
+ * @group SystemTest
+ * @group medium
+ *
+ * @licence GNU GPL v2+
+ * @author Katie Filbert < aude.wiki@gmail.com >
+ */
+class SpecialPageAliasTest extends MediaWikiTestCase {
+
+ /**
+ * @dataProvider validSpecialPageAliasesProvider
+ */
+ public function testValidSpecialPageAliases( $code, $specialPageAliases ) {
+ foreach( $specialPageAliases as $specialPage => $aliases ) {
+ foreach( $aliases as $alias ) {
+ $msg = "$specialPage alias '$alias' in $code is valid with no slashes";
+ $this->assertRegExp( '/^[^\/]*$/', $msg );
+ }
+ }
+ }
+
+ public function validSpecialPageAliasesProvider() {
+ $codes = array_keys( Language::fetchLanguageNames( 'mwfile' ) );
+
+ $data = array();
+
+ foreach( $codes as $code ) {
+ $specialPageAliases = $this->getSpecialPageAliases( $code );
+
+ if ( $specialPageAliases !== array() ) {
+ $data[] = array( $code, $specialPageAliases );
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ */
+ protected function getSpecialPageAliases( $code ) {
+ $file = Language::getMessagesFileName( $code );
+
+ if ( is_readable( $file ) ) {
+ include $file;
+
+ if ( isset( $specialPageAliases ) && $specialPageAliases !== null ) {
+ return $specialPageAliases;
+ }
+ }
+
+ return array();
+ }
+
+}