and 'subcats'
* (bug 38362) Make Special:Listuser includeable on wiki pages.
* Added support in jquery.localize for placeholder attributes.
+* (bug 38151) Implemented mw.user.getRights for getting and caching the current
+ user's user rights.
+* Implemented mw.user.getGroups for getting and caching user groups.
+* (bug 37830) Added $wgRequirePasswordforEmailChange to control whether password
+ confirmation is required for changing an email address or not.
=== Bug fixes in 1.20 ===
* (bug 30245) Use the correct way to construct a log page title.
* (bug 38093) Gender of changed user groups missing in Special:Log/rights
* (bug 35893) Special:Block needs to load mediawiki.special.block.js.
* (bug 37331) ResourceLoader modules sometimes execute twice in Firefox
+* (bug 31644) GlobalUsage, CentralAuth and AbuseLog extensions should not use
+ insecure links to foreign wikis in the WikiMap.
=== API changes in 1.20 ===
* (bug 34316) Add ability to retrieve maximum upload size from MediaWiki API.
and only applies to MyISAM or similar DBs. Those should only be used
for archived sites anyway. We can't get edit conflicts on such sites,
so the WikiPage code wasn't useful there either.
+* Deprecated mw.user.name in favour of mw.user.getName.
+* Deprecated mw.user.anonymous in favour of mw.user.isAnon.
== Compatibility ==
*/
$wgSend404Code = true;
+
+/**
+ * The $wgShowRollbackEditCount variable is used to show how many edits will be
+ * rollback. The numeric value of the varible are the limit up to are counted.
+ * If the value is false or 0, the edits are not counted.
+ */
+$wgShowRollbackEditCount = 10;
+
/** @} */ # End of output format settings }
/*************************************************************************//**
$wgDBtestuser = ''; //db user that has permission to create and drop the test databases only
$wgDBtestpassword = '';
+/**
+ * Whether the user must enter their password to change their e-mail address
+ */
+$wgRequirePasswordforEmailChange = true;
+
/**
* For really cool vim folding this needs to be at the end:
* vim: foldmarker=@{,@} foldmethod=marker
* @return String: HTML fragment
*/
public static function buildRollbackLink( $rev, IContextSource $context = null ) {
+ global $wgShowRollbackEditCount;
+
if ( $context === null ) {
$context = RequestContext::getMain();
}
$query['bot'] = '1';
$query['hidediff'] = '1'; // bug 15999
}
- return self::link(
- $title,
- $context->msg( 'rollbacklink' )->escaped(),
- array( 'title' => $context->msg( 'tooltip-rollback' )->text() ),
- $query,
- array( 'known', 'noclasses' )
- );
+
+ if( is_int( $wgShowRollbackEditCount ) && $wgShowRollbackEditCount > 0 ) {
+ $dbr = wfGetDB( DB_SLAVE );
+
+ // Up to the value of $wgShowRollbackEditCount revisions are counted
+ $res = $dbr->select( 'revision',
+ array( 'rev_id', 'rev_user_text' ),
+ array( 'rev_page' => $rev->getPage() ),
+ __METHOD__,
+ array( 'USE INDEX' => 'page_timestamp',
+ 'ORDER BY' => 'rev_timestamp DESC',
+ 'LIMIT' => $wgShowRollbackEditCount + 1 )
+ );
+
+ $editCount = 0;
+ while( $row = $dbr->fetchObject( $res ) ) {
+ if( $rev->getUserText() != $row->rev_user_text ) {
+ break;
+ }
+ $editCount++;
+ }
+
+ if( $editCount > $wgShowRollbackEditCount ) {
+ $editCount_output = $context->msg( 'rollbacklinkcount-morethan' )->numParams( $wgShowRollbackEditCount )->parse();
+ } else {
+ $editCount_output = $context->msg( 'rollbacklinkcount' )->numParams( $editCount )->parse();
+ }
+
+ return self::link(
+ $title,
+ $editCount_output,
+ array( 'title' => $context->msg( 'tooltip-rollback' )->text() ),
+ $query,
+ array( 'known', 'noclasses' )
+ );
+ } else {
+ return self::link(
+ $title,
+ $context->msg( 'rollbacklink' )->escaped(),
+ array( 'title' => $context->msg( 'tooltip-rollback' )->text() ),
+ $query,
+ array( 'known', 'noclasses' )
+ );
+ }
}
/**
$wiki = WikiMap::getWiki( $wikiID );
if ( $wiki ) {
- return $wiki->getUrl( $page );
+ return $wiki->getFullUrl( $page );
}
return false;
*/
private function getContextResult(){
$warnings = $this->getApiWarnings();
- if ( $warnings ) {
+ if ( $warnings && !$this->mParams['ignorewarnings'] ) {
// Get warnings formated in result array format
return $this->getWarningsResult( $warnings );
} elseif ( $this->mParams['chunk'] ) {
// Add chunk, and get result
- return $this->getChunkResult();
+ return $this->getChunkResult( $warnings );
} elseif ( $this->mParams['stash'] ) {
// Stash the file and get stash result
- return $this->getStashResult();
+ return $this->getStashResult( $warnings );
}
// This is the most common case -- a normal upload with no warnings
// performUpload will return a formatted properly for the API with status
- return $this->performUpload();
+ return $this->performUpload( $warnings );
}
/**
* Get Stash Result, throws an expetion if the file could not be stashed.
+ * @param $warnings array Array of Api upload warnings
* @return array
*/
- private function getStashResult(){
+ private function getStashResult( $warnings ){
$result = array ();
// Some uploads can request they be stashed, so as not to publish them immediately.
// In this case, a failure to stash ought to be fatal
$result['result'] = 'Success';
$result['filekey'] = $this->performStash();
$result['sessionkey'] = $result['filekey']; // backwards compatibility
+ if ( $warnings && count( $warnings ) > 0 ) {
+ $result['warnings'] = $warnings;
+ }
} catch ( MWException $e ) {
$this->dieUsage( $e->getMessage(), 'stashfailed' );
}
}
/**
* Get Warnings Result
- * @param $warnings Array of Api upload warnings
+ * @param $warnings array Array of Api upload warnings
* @return array
*/
private function getWarningsResult( $warnings ){
}
/**
* Get the result of a chunk upload.
+ * @param $warnings array Array of Api upload warnings
* @return array
*/
- private function getChunkResult(){
+ private function getChunkResult( $warnings ){
$result = array();
$result['result'] = 'Continue';
+ if ( $warnings && count( $warnings ) > 0 ) {
+ $result['warnings'] = $warnings;
+ }
$request = $this->getMain()->getRequest();
$chunkPath = $request->getFileTempname( 'chunk' );
$chunkSize = $request->getUpload( 'chunk' )->getSize();
/**
- * Check warnings if ignorewarnings is not set.
+ * Check warnings.
* Returns a suitable array for inclusion into API results if there were warnings
* Returns the empty array if there were no warnings
*
protected function getApiWarnings() {
$warnings = array();
- if ( !$this->mParams['ignorewarnings'] ) {
- $warnings = $this->mUpload->checkWarnings();
- }
+ $warnings = $this->mUpload->checkWarnings();
+
return $this->transformWarnings( $warnings );
}
* Perform the actual upload. Returns a suitable result array on success;
* dies on failure.
*
+ * @param $warnings array Array of Api upload warnings
* @return array
*/
- protected function performUpload() {
+ protected function performUpload( $warnings ) {
// Use comment as initial page text by default
if ( is_null( $this->mParams['text'] ) ) {
$this->mParams['text'] = $this->mParams['comment'];
$result['result'] = 'Success';
$result['filename'] = $file->getName();
+ if ( $warnings && count( $warnings ) > 0 ) {
+ $result['warnings'] = $warnings;
+ }
return $result;
}
* @ingroup SpecialPage
*/
class SpecialChangeEmail extends UnlistedSpecialPage {
+
+ /**
+ * Users password
+ * @var string
+ */
+ protected $mPassword;
+
+ /**
+ * Users new email address
+ * @var string
+ */
+ protected $mNewEmail;
+
public function __construct() {
parent::__construct( 'ChangeEmail' );
}
+ /**
+ * @return Bool
+ */
function isListed() {
global $wgAuth;
return $wgAuth->allowPropChange( 'emailaddress' );
$this->showForm();
}
+ /**
+ * @param $type string
+ */
protected function doReturnTo( $type = 'hard' ) {
$titleObj = Title::newFromText( $this->getRequest()->getVal( 'returnto' ) );
if ( !$titleObj instanceof Title ) {
}
}
+ /**
+ * @param $msg string
+ */
protected function error( $msg ) {
$this->getOutput()->wrapWikiMsg( "<p class='error'>\n$1\n</p>", $msg );
}
protected function showForm() {
+ global $wgRequirePasswordforEmailChange;
$user = $this->getUser();
$oldEmailText = $user->getEmail()
Html::hidden( 'token', $user->getEditToken() ) . "\n" .
Html::hidden( 'returnto', $this->getRequest()->getVal( 'returnto' ) ) . "\n" .
$this->msg( 'changeemail-text' )->parseAsBlock() . "\n" .
- Xml::openElement( 'table', array( 'id' => 'mw-changeemail-table' ) ) . "\n" .
- $this->pretty( array(
- array( 'wpName', 'username', 'text', $user->getName() ),
- array( 'wpOldEmail', 'changeemail-oldemail', 'text', $oldEmailText ),
- array( 'wpNewEmail', 'changeemail-newemail', 'input', $this->mNewEmail ),
- array( 'wpPassword', 'yourpassword', 'password', $this->mPassword ),
- ) ) . "\n" .
+ Xml::openElement( 'table', array( 'id' => 'mw-changeemail-table' ) ) . "\n"
+ );
+ $items = array(
+ array( 'wpName', 'username', 'text', $user->getName() ),
+ array( 'wpOldEmail', 'changeemail-oldemail', 'text', $oldEmailText ),
+ array( 'wpNewEmail', 'changeemail-newemail', 'input', $this->mNewEmail ),
+ );
+ if ( $wgRequirePasswordforEmailChange ) {
+ $items[] = array( 'wpPassword', 'yourpassword', 'password', $this->mPassword );
+ }
+
+ $this->getOutput()->addHTML(
+ $this->pretty( $items ) .
+ "\n" .
"<tr>\n" .
"<td></td>\n" .
'<td class="mw-input">' .
);
}
+ /**
+ * @param $fields array
+ * @return string
+ */
protected function pretty( $fields ) {
$out = '';
foreach ( $fields as $list ) {
}
/**
+ * @param $user User
+ * @param $pass string
+ * @param $newaddr string
* @return bool|string true or string on success, false on failure
*/
protected function attemptChange( User $user, $pass, $newaddr ) {
return false;
}
- if ( !$user->checkTemporaryPassword( $pass ) && !$user->checkPassword( $pass ) ) {
+ global $wgRequirePasswordforEmailChange;
+ if ( $wgRequirePasswordforEmailChange && !$user->checkTemporaryPassword( $pass ) && !$user->checkPassword( $pass ) ) {
$this->error( 'wrongpassword' );
return false;
}
'rollback' => 'Roll back edits',
'rollback_short' => 'Rollback',
'rollbacklink' => 'rollback',
+'rollbacklinkcount' => 'rollback $1 {{PLURAL:$1|edit|edits}}',
+'rollbacklinkcount-morethan' => 'rollback more than $1 {{PLURAL:$1|edit|edits}}',
'rollbackfailed' => 'Rollback failed',
'cantrollback' => 'Cannot revert edit;
last contributor is only author of this page.',
NS_CATEGORY_TALK => 'Kategorijos_aptarimas',
);
+$namespaceGenderAliases = array(
+ NS_USER => array( 'male' => 'Naudotojas', 'female' => 'Naudotoja' ),
+ NS_USER_TALK => array( 'male' => 'Naudotojo_aptarimas', 'female' => 'Naudotojos_aptarimas' ),
+);
+
$specialPageAliases = array(
'Allmessages' => array( 'Visi_pranešimai' ),
'Allpages' => array( 'Visi_puslapiai' ),
'rollback_short' => '{{Identical|Rollback}}',
'rollbacklink' => '{{Identical|Rollback}}
This message has a tooltip {{msg-mw|tooltip-rollback}}',
+'rollbacklinkcount' => '* $1: the number of edit that will be rollbacked
+If $1 is over the value of $wgShowRollbackEditCount (default: 10) [[MediaWiki:Rollbacklinkcount-morethan/en|rollbacklinkcount-morethan]] is used',
+'rollbacklinkcount-morethan' => 'Similar to [[MediaWiki:Rollbacklinkcount/en|rollbacklinkcount]] but with prefix more than',
'rollbackfailed' => '{{Identical|Rollback}}',
'cantrollback' => '{{Identical|Revert}}
{{Identical|Rollback}}',
);
/**
- * Aliases from the fallback language 'lt' to avoid breakage of links
- */
-
+ * Aliases from the fallback language 'lt' to avoid breakage of links
+ */
$namespaceAliases = array(
'Specialus' => NS_SPECIAL,
'Aptarimas' => NS_TALK,
'Kategorijos_aptarimas' => NS_CATEGORY_TALK,
);
+$namespaceGenderAliases = array();
+
$messages = array(
# User preference toggles
'tog-underline' => 'Pabrauktė nūruodas:',
'rollback',
'rollback_short',
'rollbacklink',
+ 'rollbacklinkcount',
+ 'rollbacklinkcount-morethan',
'rollbackfailed',
'cantrollback',
'alreadyrolled',
'scripts' => 'resources/mediawiki/mediawiki.user.js',
'dependencies' => array(
'jquery.cookie',
+ 'mediawiki.api',
),
),
'mediawiki.util' => array(
/* Private Members */
var that = this;
+ var api = new mw.Api();
+ var groupsDeferred;
+ var rightsDeferred;
/* Public Members */
*
* @return Mixed: User name string or null if users is anonymous
*/
- this.name = function() {
+ this.getName = function () {
return mw.config.get( 'wgUserName' );
};
+ /**
+ * @deprecated since 1.20 use mw.user.getName() instead
+ */
+ this.name = function () {
+ return this.getName();
+ };
+
/**
* Checks if the current user is anonymous.
*
* @return Boolean
*/
- this.anonymous = function() {
- return that.name() ? false : true;
+ this.isAnon = function () {
+ return that.getName() === null;
+ };
+
+ /**
+ * @deprecated since 1.20 use mw.user.isAnon() instead
+ */
+ this.anonymous = function () {
+ return that.isAnon();
};
/**
* @return String: User name or random session ID
*/
this.id = function() {
- var name = that.name();
+ var name = that.getName();
if ( name ) {
return name;
}
}
return bucket;
};
+
+ /**
+ * Gets the current user's groups.
+ */
+ this.getGroups = function ( callback ) {
+ if ( groupsDeferred ) {
+ groupsDeferred.always( callback );
+ return;
+ }
+
+ groupsDeferred = $.Deferred();
+ groupsDeferred.always( callback );
+ api.get( {
+ action: 'query',
+ meta: 'userinfo',
+ uiprop: 'groups'
+ } ).done( function ( data ) {
+ if ( data.query && data.query.userinfo && data.query.userinfo.groups ) {
+ groupsDeferred.resolve( data.query.userinfo.groups );
+ } else {
+ groupsDeferred.reject( [] );
+ }
+ } ).fail( function ( data ) {
+ groupsDeferred.reject( [] );
+ } );
+ };
+
+ /**
+ * Gets the current user's rights.
+ */
+ this.getRights = function ( callback ) {
+ if ( rightsDeferred ) {
+ rightsDeferred.always( callback );
+ return;
+ }
+
+ rightsDeferred = $.Deferred();
+ rightsDeferred.always( callback );
+ api.get( {
+ action: 'query',
+ meta: 'userinfo',
+ uiprop: 'rights'
+ } ).done( function ( data ) {
+ if ( data.query && data.query.userinfo && data.query.userinfo.rights ) {
+ rightsDeferred.resolve( data.query.userinfo.rights );
+ } else {
+ rightsDeferred.reject( [] );
+ }
+ } ).fail( function ( data ) {
+ rightsDeferred.reject( [] );
+ } );
+ };
}
// Extend the skeleton mw.user from mediawiki.js
--- /dev/null
+<?php
+/**
+ * Based on the test suite of the original Python
+ * CSSJanus libary:
+ * http://code.google.com/p/cssjanus/source/browse/trunk/cssjanus_test.py
+ * Ported to PHP for ResourceLoader and has been extended since.
+ */
+class CSSJanusTest extends MediaWikiTestCase {
+ /**
+ * @dataProvider provideTransformCases
+ */
+ function testTransform( $cssA, $cssB = null ) {
+
+ if ( $cssB ) {
+ $transformedA = CSSJanus::transform( $cssA );
+ $this->assertEquals( $transformedA, $cssB, 'Test A-B transformation' );
+
+ $transformedB = CSSJanus::transform( $cssB );
+ $this->assertEquals( $transformedB, $cssA, 'Test B-A transformation' );
+
+ // If no B version is provided, it means
+ // the output should equal the input.
+ } else {
+ $transformedA = CSSJanus::transform( $cssA );
+ $this->assertEquals( $transformedA, $cssA, 'Nothing was flipped' );
+ }
+ }
+
+ /**
+ * @dataProvider provideTransformAdvancedCases
+ */
+ function testTransformAdvanced( $code, $expectedOutput, $options = array() ) {
+ $swapLtrRtlInURL = isset( $options['swapLtrRtlInURL'] ) ? $options['swapLtrRtlInURL'] : false;
+ $swapLeftRightInURL = isset( $options['swapLeftRightInURL'] ) ? $options['swapLeftRightInURL'] : false;
+
+ $flipped = CSSJanus::transform( $code, $swapLtrRtlInURL, $swapLeftRightInURL );
+
+ $this->assertEquals( $expectedOutput, $flipped,
+ 'Test flipping, options: url-ltr-rtl=' . ($swapLtrRtlInURL ? 'true' : 'false')
+ . ' url-left-right=' . ($swapLeftRightInURL ? 'true' : 'false')
+ );
+ }
+ /**
+ * @dataProvider provideTransformBrokenCases
+ * @group Broken
+ */
+ function testTransformBroken( $code, $expectedOutput ) {
+ $flipped = CSSJanus::transform( $code );
+
+ $this->assertEquals( $expectedOutput, $flipped, 'Test flipping' );
+ }
+
+ /**
+ * These transform cases are tested *in both directions*
+ * No need to declare a principle twice in both directions here.
+ */
+ function provideTransformCases() {
+ return array(
+ // Property keys
+ array(
+ '.foo { left: 0; }',
+ '.foo { right: 0; }'
+ ),
+ // Guard against partial keys
+ // (CSS currently doesn't have flippable properties
+ // that contain the direction as part of the key without
+ // dash separation)
+ array(
+ '.foo { alright: 0; }'
+ ),
+ array(
+ '.foo { balleft: 0; }'
+ ),
+
+ // Dashed property keys
+ array(
+ '.foo { padding-left: 0; }',
+ '.foo { padding-right: 0; }'
+ ),
+ array(
+ '.foo { margin-left: 0; }',
+ '.foo { margin-right: 0; }'
+ ),
+ array(
+ '.foo { border-left: 0; }',
+ '.foo { border-right: 0; }'
+ ),
+
+ // Double-dashed property keys
+ array(
+ '.foo { border-left-color: red; }',
+ '.foo { border-right-color: red; }'
+ ),
+ array(
+ // Includes unknown properties?
+ '.foo { x-left-y: 0; }',
+ '.foo { x-right-y: 0; }'
+ ),
+
+ // Multi-value properties
+ array(
+ '.foo { padding: 0; }'
+ ),
+ array(
+ '.foo { padding: 0 1px; }'
+ ),
+ array(
+ '.foo { padding: 0 1px 2px; }'
+ ),
+ array(
+ '.foo { padding: 0 1px 2px 3px; }',
+ '.foo { padding: 0 3px 2px 1px; }'
+ ),
+
+ // Shorthand / Four notation
+ array(
+ '.foo { padding: .25em 15px 0pt 0ex; }',
+ '.foo { padding: .25em 0ex 0pt 15px; }'
+ ),
+ array(
+ '.foo { margin: 1px -4px 3px 2px; }',
+ '.foo { margin: 1px 2px 3px -4px; }'
+ ),
+ array(
+ '.foo { padding: 0 15px .25em 0; }',
+ '.foo { padding: 0 0 .25em 15px; }'
+ ),
+ array(
+ '.foo { padding: 1px 4.1grad 3px 2%; }',
+ '.foo { padding: 1px 2% 3px 4.1grad; }'
+ ),
+ array(
+ '.foo { padding: 1px 2px 3px auto; }',
+ '.foo { padding: 1px auto 3px 2px; }'
+ ),
+ array(
+ '.foo { padding: 1px inherit 3px auto; }',
+ '.foo { padding: 1px auto 3px inherit; }'
+ ),
+ array(
+ '.foo { border-radius: .25em 15px 0pt 0ex; }',
+ '.foo { border-radius: .25em 0ex 0pt 15px; }'
+ ),
+ array(
+ '.foo { x-unknown: a b c d; }'
+ ),
+ array(
+ '.foo barpx 0 2% { opacity: 0; }'
+ ),
+ array(
+ '#settings td p strong'
+ ),
+ array(
+ # Not sure how 4+ values should behave,
+ # testing to make sure changes are detected
+ '.foo { x-unknown: 1 2 3 4 5; }',
+ '.foo { x-unknown: 1 4 3 2 5; }',
+ ),
+ array(
+ '.foo { x-unknown: 1 2 3 4 5 6; }',
+ '.foo { x-unknown: 1 4 3 2 5 6; }',
+ ),
+
+ // Shorthand / Three notation
+ array(
+ '.foo { margin: 1em 0 .25em; }'
+ ),
+ array(
+ '.foo { margin:-1.5em 0 -.75em; }'
+ ),
+
+ // Shorthand / Two notation
+ array(
+ '.foo { padding: 1px 2px; }'
+ ),
+
+ // Shorthand / One notation
+ array(
+ '.foo { padding: 1px; }'
+ ),
+
+ // Direction
+ // Note: This differs from the Python implementation,
+ // see also CSSJanus::fixDirection for more info.
+ array(
+ '.foo { direction: ltr; }',
+ '.foo { direction: rtl; }'
+ ),
+ array(
+ '.foo { direction: rtl; }',
+ '.foo { direction: ltr; }'
+ ),
+ array(
+ 'input { direction: ltr; }',
+ 'input { direction: rtl; }'
+ ),
+ array(
+ 'input { direction: rtl; }',
+ 'input { direction: ltr; }'
+ ),
+ array(
+ 'body { direction: ltr; }',
+ 'body { direction: rtl; }'
+ ),
+ array(
+ '.foo, body, input { direction: ltr; }',
+ '.foo, body, input { direction: rtl; }'
+ ),
+ array(
+ 'body { padding: 10px; direction: ltr; }',
+ 'body { padding: 10px; direction: rtl; }'
+ ),
+ array(
+ 'body { direction: ltr } .myClass { direction: ltr }',
+ 'body { direction: rtl } .myClass { direction: rtl }'
+ ),
+
+ // Left/right values
+ array(
+ '.foo { float: left; }',
+ '.foo { float: right; }'
+ ),
+ array(
+ '.foo { text-align: left; }',
+ '.foo { text-align: right; }'
+ ),
+ array(
+ '.foo { -x-unknown: left; }',
+ '.foo { -x-unknown: right; }'
+ ),
+ // Guard against selectors that look flippable
+ array(
+ '.column-left { width: 0; }'
+ ),
+ array(
+ 'a.left { width: 0; }'
+ ),
+ array(
+ 'a.leftification { width: 0; }'
+ ),
+ array(
+ 'a.ltr { width: 0; }'
+ ),
+ array(
+ # <div class="a-ltr png">
+ '.a-ltr.png { width: 0; }'
+ ),
+ array(
+ # <foo-ltr attr="x">
+ 'foo-ltr[attr="x"] { width: 0; }'
+ ),
+ array(
+ 'div.left > span.right+span.left { width: 0; }'
+ ),
+ array(
+ '.thisclass .left .myclass { width: 0; }'
+ ),
+ array(
+ '.thisclass .left .myclass #myid { width: 0; }'
+ ),
+
+ // Cursor values (east/west)
+ array(
+ '.foo { cursor: e-resize; }',
+ '.foo { cursor: w-resize; }'
+ ),
+ array(
+ '.foo { cursor: se-resize; }',
+ '.foo { cursor: sw-resize; }'
+ ),
+ array(
+ '.foo { cursor: ne-resize; }',
+ '.foo { cursor: nw-resize; }'
+ ),
+
+ // Background
+ array(
+ '.foo { background-position: top left; }',
+ '.foo { background-position: top right; }'
+ ),
+ array(
+ '.foo { background: url(/foo/bar.png) top left; }',
+ '.foo { background: url(/foo/bar.png) top right; }'
+ ),
+ array(
+ '.foo { background: url(/foo/bar.png) top left no-repeat; }',
+ '.foo { background: url(/foo/bar.png) top right no-repeat; }'
+ ),
+ array(
+ '.foo { background: url(/foo/bar.png) no-repeat top left; }',
+ '.foo { background: url(/foo/bar.png) no-repeat top right; }'
+ ),
+ array(
+ '.foo { background: #fff url(/foo/bar.png) no-repeat top left; }',
+ '.foo { background: #fff url(/foo/bar.png) no-repeat top right; }'
+ ),
+ array(
+ '.foo { background-position: 100% 40%; }',
+ '.foo { background-position: 0% 40%; }'
+ ),
+ array(
+ '.foo { background-position: 23% 0; }',
+ '.foo { background-position: 77% 0; }'
+ ),
+ array(
+ '.foo { background-position: 23% auto; }',
+ '.foo { background-position: 77% auto; }'
+ ),
+ array(
+ '.foo { background-position-x: 23%; }',
+ '.foo { background-position-x: 77%; }'
+ ),
+ array(
+ '.foo { background-position-y: 23%; }',
+ '.foo { background-position-y: 23%; }'
+ ),
+ array(
+ '.foo { background:url(../foo.png) no-repeat 75% 50%; }',
+ '.foo { background:url(../foo.png) no-repeat 25% 50%; }'
+ ),
+ array(
+ '.foo { background: 10% 20% } .bar { background: 40% 30% }',
+ '.foo { background: 90% 20% } .bar { background: 60% 30% }'
+ ),
+
+ // Multiple rules
+ array(
+ 'body { direction: rtl; float: right; } .foo { direction: ltr; float: right; }',
+ 'body { direction: ltr; float: left; } .foo { direction: rtl; float: left; }',
+ ),
+
+ // Duplicate properties
+ array(
+ '.foo { float: left; float: right; float: left; }',
+ '.foo { float: right; float: left; float: right; }',
+ ),
+
+ // Preserve comments
+ array(
+ '/* left /* right */left: 10px',
+ '/* left /* right */right: 10px'
+ ),
+ array(
+ '/*left*//*left*/left: 10px',
+ '/*left*//*left*/right: 10px'
+ ),
+ array(
+ '/* Going right is cool */ .foo { width: 0 }',
+ ),
+ array(
+ "/* padding-right 1 2 3 4 */\n#test { width: 0}\n/*right*/"
+ ),
+ array(
+ "/** Two line comment\n * left\n \*/\n#test {width: 0}"
+ ),
+
+ // @noflip annotation
+ array(
+ // before selector (single)
+ '/* @noflip */ div { float: left; }'
+ ),
+ array(
+ // before selector (multiple)
+ '/* @noflip */ div, .notme { float: left; }'
+ ),
+ array(
+ // inside selector
+ 'div, /* @noflip */ .foo { float: left; }'
+ ),
+ array(
+ // after selector
+ 'div, .notme /* @noflip */ { float: left; }'
+ ),
+ array(
+ // before multiple rules
+ '/* @noflip */ div { float: left; } .foo { float: left; }',
+ '/* @noflip */ div { float: left; } .foo { float: right; }'
+ ),
+ array(
+ // after multiple rules
+ '.foo { float: left; } /* @noflip */ div { float: left; }',
+ '.foo { float: right; } /* @noflip */ div { float: left; }'
+ ),
+ array(
+ // before multiple properties
+ 'div { /* @noflip */ float: left; text-align: left; }',
+ 'div { /* @noflip */ float: left; text-align: right; }'
+ ),
+ array(
+ // after multiple properties
+ 'div { float: left; /* @noflip */ text-align: left; }',
+ 'div { float: right; /* @noflip */ text-align: left; }'
+ ),
+
+ // Guard against css3 stuff
+ array(
+ 'background-image: -moz-linear-gradient(#326cc1, #234e8c);'
+ ),
+ array(
+ 'background-image: -webkit-gradient(linear, 100% 0%, 0% 0%, from(#666666), to(#ffffff));'
+ ),
+
+ // CSS syntax / white-space variations
+ // spaces, no spaces, tabs, new lines, omitting semi-colons
+ array(
+ ".foo { left: 0; }",
+ ".foo { right: 0; }"
+ ),
+ array(
+ ".foo{ left: 0; }",
+ ".foo{ right: 0; }"
+ ),
+ array(
+ ".foo{ left: 0 }",
+ ".foo{ right: 0 }"
+ ),
+ array(
+ ".foo{left:0 }",
+ ".foo{right:0 }"
+ ),
+ array(
+ ".foo{left:0}",
+ ".foo{right:0}"
+ ),
+ array(
+ ".foo { left : 0 ; }",
+ ".foo { right : 0 ; }"
+ ),
+ array(
+ ".foo\n { left : 0 ; }",
+ ".foo\n { right : 0 ; }"
+ ),
+ array(
+ ".foo\n { \nleft : 0 ; }",
+ ".foo\n { \nright : 0 ; }"
+ ),
+ array(
+ ".foo\n { \n left : 0 ; }",
+ ".foo\n { \n right : 0 ; }"
+ ),
+ array(
+ ".foo\n { \n left\n : 0; }",
+ ".foo\n { \n right\n : 0; }"
+ ),
+ array(
+ ".foo \n { \n left\n : 0; }",
+ ".foo \n { \n right\n : 0; }"
+ ),
+ array(
+ ".foo\n{\nleft\n:\n0;}",
+ ".foo\n{\nright\n:\n0;}"
+ ),
+ array(
+ ".foo\n.bar {\n\tleft: 0;\n}",
+ ".foo\n.bar {\n\tright: 0;\n}"
+ ),
+ array(
+ ".foo\t{\tleft\t:\t0;}",
+ ".foo\t{\tright\t:\t0;}"
+ ),
+ );
+ }
+
+ /**
+ * These cases are tested in one way only (format: actual, expected, msg).
+ * If both ways can be tested, either put both versions in here or move
+ * it to provideTransformCases().
+ */
+ function provideTransformAdvancedCases() {
+ $bgPairs = array(
+ # [ - _ . ] <-> [ left right ltr rtl ]
+ 'foo.jpg' => 'foo.jpg',
+ 'left.jpg' => 'right.jpg',
+ 'ltr.jpg' => 'rtl.jpg',
+
+ 'foo-left.png' => 'foo-right.png',
+ 'foo_left.png' => 'foo_right.png',
+ 'foo.left.png' => 'foo.right.png',
+
+ 'foo-ltr.png' => 'foo-rtl.png',
+ 'foo_ltr.png' => 'foo_rtl.png',
+ 'foo.ltr.png' => 'foo.rtl.png',
+
+ 'left-foo.png' => 'right-foo.png',
+ 'left_foo.png' => 'right_foo.png',
+ 'left.foo.png' => 'right.foo.png',
+
+ 'ltr-foo.png' => 'rtl-foo.png',
+ 'ltr_foo.png' => 'rtl_foo.png',
+ 'ltr.foo.png' => 'rtl.foo.png',
+
+ 'foo-ltr-left.gif' => 'foo-rtl-right.gif',
+ 'foo_ltr_left.gif' => 'foo_rtl_right.gif',
+ 'foo.ltr.left.gif' => 'foo.rtl.right.gif',
+ 'foo-ltr_left.gif' => 'foo-rtl_right.gif',
+ 'foo_ltr.left.gif' => 'foo_rtl.right.gif',
+ );
+ $provider = array();
+ foreach ( $bgPairs as $left => $right ) {
+ # By default '-rtl' and '-left' etc. are not touched,
+ # Only when the appropiate parameter is set.
+ $provider[] = array(
+ ".foo { background: url(images/$left); }",
+ ".foo { background: url(images/$left); }"
+ );
+ $provider[] = array(
+ ".foo { background: url(images/$right); }",
+ ".foo { background: url(images/$right); }"
+ );
+ $provider[] = array(
+ ".foo { background: url(images/$left); }",
+ ".foo { background: url(images/$right); }",
+ array(
+ 'swapLtrRtlInURL' => true,
+ 'swapLeftRightInURL' => true,
+ )
+ );
+ $provider[] = array(
+ ".foo { background: url(images/$right); }",
+ ".foo { background: url(images/$left); }",
+ array(
+ 'swapLtrRtlInURL' => true,
+ 'swapLeftRightInURL' => true,
+ )
+ );
+ }
+
+ return $provider;
+ }
+
+ /**
+ * Cases that are currently failing, but
+ * should be looked at in the future as enhancements and/or bug fix
+ */
+ function provideTransformBrokenCases() {
+ return array(
+ // Guard against partial keys
+ array(
+ '.foo { leftxx: 0; }',
+ '.foo { leftxx: 0; }'
+ ),
+ array(
+ '.foo { rightxx: 0; }',
+ '.foo { rightxx: 0; }'
+ ),
+
+ // Guard against selectors that look flippable
+ array(
+ # <foo-left-x attr="x">
+ 'foo-left-x[attr="x"] { width: 0; }',
+ 'foo-left-x[attr="x"] { width: 0; }'
+ ),
+ array(
+ # <div class="foo" data-left="x">
+ '.foo[data-left="x"] { width: 0; }',
+ '.foo[data-left="x"] { width: 0; }'
+ ),
+ );
+ }
+}
assert.ok( mw.user.options instanceof mw.Map, 'options instance of mw.Map' );
});
-QUnit.test( 'User login status', 5, function ( assert ) {
+QUnit.test( 'user status', 9, function ( assert ) {
/**
* Tests can be run under three different conditions:
* 1) From tests/qunit/index.html, user will be anonymous.
*/
// Forge an anonymous user:
- mw.config.set( 'wgUserName', null);
+ mw.config.set( 'wgUserName', null );
- assert.strictEqual( mw.user.name(), null, 'user.name should return null when anonymous' );
- assert.ok( mw.user.anonymous(), 'user.anonymous should reutrn true when anonymous' );
+ assert.strictEqual( mw.user.getName(), null, 'user.getName() returns null when anonymous' );
+ assert.strictEqual( mw.user.name(), null, 'user.name() compatibility' );
+ assert.assertTrue( mw.user.isAnon(), 'user.isAnon() returns true when anonymous' );
+ assert.assertTrue( mw.user.anonymous(), 'user.anonymous() compatibility' );
// Not part of startUp module
mw.config.set( 'wgUserName', 'John' );
- assert.equal( mw.user.name(), 'John', 'user.name returns username when logged-in' );
- assert.ok( !mw.user.anonymous(), 'user.anonymous returns false when logged-in' );
+ assert.equal( mw.user.getName(), 'John', 'user.getName() returns username when logged-in' );
+ assert.equal( mw.user.name(), 'John', 'user.name() compatibility' );
+ assert.assertFalse( mw.user.isAnon(), 'user.isAnon() returns false when logged-in' );
+ assert.assertFalse( mw.user.anonymous(), 'user.anonymous() compatibility' );
assert.equal( mw.user.id(), 'John', 'user.id Returns username when logged-in' );
});
+QUnit.asyncTest( 'getGroups', 3, function ( assert ) {
+ mw.user.getGroups( function ( groups ) {
+ // First group should always be '*'
+ assert.equal( $.type( groups ), 'array', 'Callback gets an array' );
+ assert.equal( groups[0], '*', '"*"" is the first group' );
+ // Sort needed because of different methods if creating the arrays,
+ // only the content matters.
+ assert.deepEqual( groups.sort(), mw.config.get( 'wgUserGroups' ).sort(), 'Array contains all groups, just like wgUserGroups' );
+ QUnit.start();
+ });
+});
+
+QUnit.asyncTest( 'getRights', 1, function ( assert ) {
+ mw.user.getRights( function ( rights ) {
+ // First group should always be '*'
+ assert.equal( $.type( rights ), 'array', 'Callback gets an array' );
+ QUnit.start();
+ });
+});
+
}( mediaWiki ) );