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.
+* (bug 36073) Avoid duplicate element IDs on File pages
+* (bug 25095) Special:Categories should also include the first relevant item
+ when "from" is filled.
=== 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 ==
// @todo FIXME: i18n issue/patchwork message
$this->getContext()->getOutput()->addHTML( '<strong class="mw-delete-warning-revisions">' .
wfMsgExt( 'historywarning', array( 'parseinline' ), $this->getContext()->getLanguage()->formatNum( $revisions ) ) .
- wfMsgHtml( 'word-separator' ) . Linker::link( $title,
+ wfMsgHtml( 'word-separator' ) . Linker::linkKnown( $title,
wfMsgHtml( 'history' ),
array( 'rel' => 'archives' ),
array( 'action' => 'history' ) ) .
'UserArray' => 'includes/UserArray.php',
'UserArrayFromResult' => 'includes/UserArray.php',
'UserBlockedError' => 'includes/Exception.php',
+ 'UserNotLoggedIn' => 'includes/Exception.php',
'UserMailer' => 'includes/UserMailer.php',
'UserRightsProxy' => 'includes/UserRightsProxy.php',
'ViewCountUpdate' => 'includes/ViewCountUpdate.php',
*/
$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
* Exception class which takes an HTML error message, and does not
* produce a backtrace. Replacement for OutputPage::fatalError().
*
+ * @since 1.7
* @ingroup Exception
*/
class FatalError extends MWException {
/**
* An error page which can definitely be safely rendered using the OutputPage.
*
+ * @since 1.7
* @ingroup Exception
*/
class ErrorPageError extends MWException {
* Similar to ErrorPage, but emit a 400 HTTP error code to let mobile
* browser it is not really a valid content.
*
+ * @since 1.19
* @ingroup Exception
*/
class BadTitleError extends ErrorPageError {
* Show an error when a user tries to do something they do not have the necessary
* permissions for.
*
+ * @since 1.18
* @ingroup Exception
*/
class PermissionsError extends ErrorPageError {
* Show an error when the wiki is locked/read-only and the user tries to do
* something that requires write access.
*
+ * @since 1.18
* @ingroup Exception
*/
class ReadOnlyError extends ErrorPageError {
/**
* Show an error when the user hits a rate limit.
*
+ * @since 1.18
* @ingroup Exception
*/
class ThrottledError extends ErrorPageError {
/**
* Show an error when the user tries to do something whilst blocked.
*
+ * @since 1.18
* @ingroup Exception
*/
class UserBlockedError extends ErrorPageError {
* This is essentially an ErrorPageError exception which by default use the
* 'exception-nologin' as a title and 'exception-nologin-text' for the message.
* @see bug 37627
+ * @since 1.20
*
* @par Example:
* @code
* Show an error that looks like an HTTP server error.
* Replacement for wfHttpError().
*
+ * @since 1.19
* @ingroup Exception
*/
class HttpError extends MWException {
$link2 = Linker::linkKnown( Title::makeTitle( $row->page_namespace, $row->page_title ) );
$ul .= Html::rawElement(
'li',
- array( 'id' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ),
+ array( 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ),
$link2
) . "\n";
}
}
$out->addHTML( Html::rawElement(
'li',
- array( 'id' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ),
+ array( 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ),
$liContents
) . "\n"
);
* @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' )
+ );
+ }
}
/**
protected $mLastShown, $mFirstShown, $mPastTheEndIndex, $mDefaultQuery, $mNavigationBar;
+ /**
+ * Whether to include the offset in the query
+ */
+ protected $mIncludeOffset = false;
+
/**
* Result object for the query. Warning: seek before use.
*
$this->mLimit = $limit;
}
+ /**
+ * Set whether a row matching exactly the offset should be also included
+ * in the result or not. By default this is not the case, but when the
+ * offset is user-supplied this might be wanted.
+ *
+ * @param $include bool
+ */
+ public function setIncludeOffset( $include ) {
+ $this->mIncludeOffset = $include;
+ }
+
/**
* Extract some useful data from the result object for use by
* the navigation bar, put it into $this
$sortColumns = array_merge( array( $this->mIndexField ), $this->mExtraSortFields );
if ( $descending ) {
$options['ORDER BY'] = $sortColumns;
- $operator = '>';
+ $operator = $this->mIncludeOffset ? '>=' : '>';
} else {
$orderBy = array();
foreach ( $sortColumns as $col ) {
$orderBy[] = $col . ' DESC';
}
$options['ORDER BY'] = $orderBy;
- $operator = '<';
+ $operator = $this->mIncludeOffset ? '<=' : '<';
}
if ( $offset != '' ) {
$conds[] = $this->mIndexField . $operator . $this->mDb->addQuotes( $offset );
$wiki = WikiMap::getWiki( $wikiID );
if ( $wiki ) {
- return $wiki->getUrl( $page );
+ return $wiki->getFullUrl( $page );
}
return false;
}
}
- $files = RepoGroup::singleton()->findFiles( array_keys( $images ) );
+ $filesToFind = array_keys( $images );
+ if( $params['localonly'] ) {
+ $files = RepoGroup::singleton()->getLocalRepo()->findFiles( $filesToFind );
+ } else {
+ $files = RepoGroup::singleton()->findFiles( $filesToFind );
+ }
$fit = true;
$count = 0;
}
// find all files with the hashes, result format is: array( hash => array( dup1, dup2 ), hash1 => ... )
- $filesBySha1s = RepoGroup::singleton()->findBySha1s( array_unique( array_values( $sha1s ) ) );
+ $filesToFindBySha1s = array_unique( array_values( $sha1s ) );
+ if( $params['localonly'] ) {
+ $filesBySha1s = RepoGroup::singleton()->getLocalRepo()->findBySha1s( $filesToFindBySha1s );
+ } else {
+ $filesBySha1s = RepoGroup::singleton()->findBySha1s( $filesToFindBySha1s );
+ }
// iterate over $images to handle continue param correct
foreach( $images as $image => $pageId ) {
'descending'
)
),
+ 'localonly' => false,
);
}
'limit' => 'How many duplicate files to return',
'continue' => 'When more results are available, use this to continue',
'dir' => 'The direction in which to list',
+ 'localonly' => 'Look only for files in the local repository',
);
}
}
$result = $this->getResult();
- $images = RepoGroup::singleton()->findFiles( $titles );
+ //search only inside the local repo
+ if( $params['localonly'] ) {
+ $images = RepoGroup::singleton()->getLocalRepo()->findFiles( $titles );
+ } else {
+ $images = RepoGroup::singleton()->findFiles( $titles );
+ }
foreach ( $images as $img ) {
// Skip redirects
if ( $img->getOriginalTitle()->isRedirect() ) {
ApiBase::PARAM_TYPE => 'string',
),
'continue' => null,
+ 'localonly' => false,
);
}
'end' => 'Timestamp to stop listing at',
'metadataversion' => array( "Version of metadata to use. if 'latest' is specified, use latest version.",
"Defaults to '1' for backwards compatibility" ),
- 'continue' => 'If the query response includes a continue value, use it here to get another page of results'
+ 'continue' => 'If the query response includes a continue value, use it here to get another page of results',
+ 'localonly' => 'Look only for files in the local repository',
);
}
*/
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;
}
$obj->set_etag( md5( $params['content'] ) );
// Use the same content type as StreamFile for security
$obj->content_type = StreamFile::contentTypeFromPath( $params['dst'] );
+ if ( !strlen( $obj->content_type ) ) { // special case
+ $obj->content_type = 'unknown/unknown';
+ }
if ( !empty( $params['async'] ) ) { // deferred
$handle = $obj->write_async( $params['content'] );
$status->value = new SwiftFileOpHandle( $this, $params, 'Create', $handle );
$obj->set_etag( md5_file( $params['src'] ) );
// Use the same content type as StreamFile for security
$obj->content_type = StreamFile::contentTypeFromPath( $params['dst'] );
+ if ( !strlen( $obj->content_type ) ) { // special case
+ $obj->content_type = 'unknown/unknown';
+ }
if ( !empty( $params['async'] ) ) { // deferred
wfSuppressWarnings();
$fp = fopen( $params['src'], 'rb' );
}
/**
- * Callback for usort() to do file sorts by title
+ * Callback for usort() to do file sorts by name
*
* @param $a File
* @param $b File
*
- * @return Integer: result of title comparison
+ * @return Integer: result of name comparison
*/
public static function compare( File $a, File $b ) {
- return Title::compare( $a->getTitle(), $b->getTitle() );
+ return strcmp( $a->getName(), $b->getName() );
}
/**
function _load_items( $sock, &$ret ) {
while ( 1 ) {
$decl = fgets( $sock );
- if ( $decl == "END\r\n" ) {
+ if( $decl === false ) {
+ $this->_debugprint( "Error reading socket for a memcached response\n" );
+ return 0;
+ } elseif ( $decl == "END\r\n" ) {
return true;
} elseif ( preg_match( '/^VALUE (\S+) (\d+) (\d+)\r\n$/', $decl, $match ) ) {
list( $rkey, $flags, $len ) = array( $match[1], $match[2], $match[3] );
}
} else {
- $this->_debugprint( "Error parsing memcached response\n" );
+ $peer = stream_socket_get_name( $sock, true /** remote **/ );
+ $this->_debugprint( "Error parsing memcached response from [{$peer}]\n" );
return 0;
}
}
$from = str_replace( ' ', '_', $from );
if( $from !== '' ) {
$from = Title::capitalize( $from, NS_CATEGORY );
- $this->mOffset = $from;
+ $this->setOffset( $from );
+ $this->setIncludeOffset( true );
}
}
* @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:',
EXAMPLE_PATTERNS = *
EXAMPLE_RECURSIVE = NO
IMAGE_PATH =
-INPUT_FILTER =
+INPUT_FILTER = "php mwdoc-filter.php"
FILTER_PATTERNS =
FILTER_SOURCE_FILES = NO
FILTER_SOURCE_PATTERNS =
$fsFile = $src->getLocalReference( array( 'src' => $srcPath, 'latest' => 1 ) );
if ( !$fsFile ) {
$this->error( "Could not get local copy of $srcPath.", 1 ); // die
+ } elseif ( !$fsFile->exists() ) {
+ // FSFileBackends just return the path for getLocalReference() and paths with
+ // illegal slashes may get normalized to a different path. This can cause the
+ // local reference to not exist...skip these broken files.
+ $this->error( "Detected possible illegal path for $srcPath." );
+ continue;
}
$fsFiles[] = $fsFile; // keep TempFSFile objects alive as needed
// Note: prepare() is usually fast for key/value backends
'rollback',
'rollback_short',
'rollbacklink',
+ 'rollbacklinkcount',
+ 'rollbacklinkcount-morethan',
'rollbackfailed',
'cantrollback',
'alreadyrolled',
--- /dev/null
+<?php
+# Original source code by Goran Rakic
+# http://blog.goranrakic.com/
+# http://stackoverflow.com/questions/4325224
+
+# Should be filled in doxygen INPUT_FILTER as "php mwdoc-filter.php"
+
+$source = file_get_contents( $argv[1] );
+$regexp = '#\@var\s+([^\s]+)([^/]+)/\s+(var|public|protected|private)\s+(\$[^\s;=]+)#';
+$replac = '${2} */ ${3} ${1} ${4}';
+$source = preg_replace($regexp, $replac, $source);
+
+echo $source;
'scripts' => 'resources/mediawiki/mediawiki.user.js',
'dependencies' => array(
'jquery.cookie',
+ 'mediawiki.api',
),
),
'mediawiki.util' => array(
.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */
.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */
button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */
-.ui-button-icons-only { width: 3.4em; }
-button.ui-button-icons-only { width: 3.7em; }
+.ui-button-icons-only { width: 3.4em; }
+button.ui-button-icons-only { width: 3.7em; }
/*button text element */
-.ui-button .ui-button-text { display: block; line-height: 1.4em; }
+.ui-button .ui-button-text { display: block; line-height: 1.4; }
.ui-button-text-only .ui-button-text { padding: 0.3em 1em 0.25em 1em; }
.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: 0.3em; text-indent: -9999999px; }
.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: 0.3em 1em 0.25em 2.1em; }
.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: 0.3em 2.1em 0.25em 1em; }
.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
-/* for older versions of jQuery UI */
-.ui-button-text-icon .ui-button-text { padding: 0.3em 1em 0.3em 2.1em; }
/* no icon support for input elements, provide padding by default */
input.ui-button { padding: 0.3em 1em; }
button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */
body .ui-button {
- -moz-border-radius: 4px;
- -webkit-border-radius: 4px;
- border-radius: 4px;
- margin: 0.5em 0 0.5em 0.4em !important;
+ margin: 0.5em 0 0.5em 0.4em;
border: 1px solid #a6a6a6 !important;
/* @embed */
background: #f2f2f2 url(images/button-off.png) repeat-x scroll 50% 100% !important;
width: auto;
overflow: visible;
}
+
+/* Corner radius */
+/* This is normally handled in jquery.ui.theme.css, but in our case, the corner
+ styling of our buttons doesn't match our default widget corner styling */
+.ui-button.ui-corner-all, .ui-button.ui-corner-top, .ui-button.ui-corner-left, .ui-button.ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; border-top-left-radius: 4px; }
+.ui-button.ui-corner-all, .ui-button.ui-corner-top, .ui-button.ui-corner-right, .ui-button.ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; border-top-right-radius: 4px; }
+.ui-button.ui-corner-all, .ui-button.ui-corner-bottom, .ui-button.ui-corner-left, .ui-button.ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
+.ui-button.ui-corner-all, .ui-button.ui-corner-bottom, .ui-button.ui-corner-right, .ui-button.ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+
body .ui-button:hover {
border-color: #6e7273;
/* @embed */
}
.mw-badge-inline {
- display: inline;
+ display: inline-block;
+ margin-left: 3px;
}
.mw-badge-overlay {
bottom: -1px;
right: -3px;
z-index: 50;
-}
\ No newline at end of file
+}
/* 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
border: 1px dashed #2f6fab;
color: black;
background-color: #f9f9f9;
-
- /*
- * Wrap properly.
- * - pre-wrap: causes the browser to naturally wrap by displaying
- * words on the next line if they don't fit on the same line
- * within the box (does not cut off words).
- * - break-word: forces the browser to wrap anywhere (even within
- * a word) if it is (still) too long for the line.
- * When only using break-word in a <pre>, the browser only uses
- * the force behavior and as a result almost always cuts half-way
- * a word. When only using pre-wrap, too-long words will still
- * cause the page layout to break. The combination is magic :).
- * See also https://bugzilla.wikimedia.org/show_bug.cgi?id=260#c20
- */
- white-space: pre-wrap;
- word-wrap: break-word;
}
/* Tables */
--- /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 ) );