'WikiRevision' => __DIR__ . '/includes/import/WikiRevision.php',
'WikiStatsOutput' => __DIR__ . '/maintenance/language/StatOutputs.php',
'WikiTextStructure' => __DIR__ . '/includes/content/WikiTextStructure.php',
+ 'Wikimedia\\Rdbms\\ConnectionManager' => __DIR__ . '/includes/libs/rdbms/connectionmanager/ConnectionManager.php',
+ 'Wikimedia\\Rdbms\\SessionConsistentConnectionManager' => __DIR__ . '/includes/libs/rdbms/connectionmanager/SessionConsistentConnectionManager.php',
'WikitextContent' => __DIR__ . '/includes/content/WikitextContent.php',
'WikitextContentHandler' => __DIR__ . '/includes/content/WikitextContentHandler.php',
'WinCacheBagOStuff' => __DIR__ . '/includes/libs/objectcache/WinCacheBagOStuff.php',
"ext-xml": "*",
"liuggio/statsd-php-client": "1.0.18",
"mediawiki/at-ease": "1.1.0",
- "oojs/oojs-ui": "0.18.0",
+ "oojs/oojs-ui": "0.18.1",
"oyejorge/less.php": "1.7.0.10",
"php": ">=5.5.9",
"psr/log": "1.0.0",
/** Transform {{..}} constructs, HTML-escape the result */
const FORMAT_ESCAPED = 'escaped';
+ /**
+ * Mapping from Message::listParam() types to Language methods.
+ * @var array
+ */
+ protected static $listTypeMap = [
+ 'comma' => 'commaList',
+ 'semicolon' => 'semicolonList',
+ 'pipe' => 'pipeList',
+ 'text' => 'listToText',
+ ];
+
/**
* In which language to get this message. True, which is the default,
* means the current user language, false content language.
return [ 'plaintext' => $plaintext ];
}
+ /**
+ * @since 1.29
+ *
+ * @param array $list
+ * @param string $type 'comma', 'semicolon', 'pipe', 'text'
+ * @return array Array with "list" and "type" keys.
+ */
+ public static function listParam( array $list, $type = 'text' ) {
+ if ( !isset( self::$listTypeMap[$type] ) ) {
+ throw new InvalidArgumentException(
+ "Invalid type '$type'. Known types are: " . join( ', ', array_keys( self::$listTypeMap ) )
+ );
+ }
+ return [ 'list' => $list, 'type' => $type ];
+ }
+
/**
* Substitutes any parameters into the message text.
*
return [ 'before', $this->getLanguage()->formatBitrate( $param['bitrate'] ) ];
} elseif ( isset( $param['plaintext'] ) ) {
return [ 'after', $this->formatPlaintext( $param['plaintext'], $format ) ];
+ } elseif ( isset( $param['list'] ) ) {
+ return $this->formatListParam( $param['list'], $param['type'], $format );
} else {
$warning = 'Invalid parameter for message "' . $this->getKey() . '": ' .
htmlspecialchars( serialize( $param ) );
}
}
+
+ /**
+ * Formats a list of parameters as a concatenated string.
+ * @since 1.29
+ * @param array $params
+ * @param string $listType
+ * @param string $format One of the FORMAT_* constants.
+ * @return array Array with the parameter type (either "before" or "after") and the value.
+ */
+ protected function formatListParam( array $params, $listType, $format ) {
+ if ( !isset( self::$listTypeMap[$listType] ) ) {
+ $warning = 'Invalid list type for message "' . $this->getKey() . '": ' .
+ htmlspecialchars( serialize( $param ) );
+ trigger_error( $warning, E_USER_WARNING );
+ $e = new Exception;
+ wfDebugLog( 'Bug58676', $warning . "\n" . $e->getTraceAsString() );
+ return [ 'before', '[INVALID]' ];
+ }
+ $func = self::$listTypeMap[$listType];
+
+ // Handle an empty list sensibly
+ if ( !$params ) {
+ return [ 'before', $this->getLanguage()->$func( [] ) ];
+ }
+
+ // First, determine what kinds of list items we have
+ $types = [];
+ $vars = [];
+ $list = [];
+ foreach ( $params as $n => $p ) {
+ list( $type, $value ) = $this->extractParam( $p, $format );
+ $types[$type] = true;
+ $list[] = $value;
+ $vars[] = '$' . ( $n + 1 );
+ }
+
+ // Easy case: all are 'before' or 'after', so just join the
+ // values and use the same type.
+ if ( count( $types ) === 1 ) {
+ return [ key( $types ), $this->getLanguage()->$func( $list ) ];
+ }
+
+ // Hard case: We need to process each value per its type, then
+ // return the concatenated values as 'after'. We handle this by turning
+ // the list into a RawMessage and processing that as a parameter.
+ $vars = $this->getLanguage()->$func( $vars );
+ return $this->extractParam( new RawMessage( $vars, $params ), $format );
+ }
}
/**
* @file
*/
+use MediaWiki\Session\Token;
+
/**
* @since 1.25
* @ingroup API
$tokenObj = ApiQueryTokens::getToken(
$this->getUser(), $this->getRequest()->getSession(), $salts[$params['type']]
);
+
+ if ( substr( $token, -strlen( urldecode( Token::SUFFIX ) ) ) === urldecode( Token::SUFFIX ) ) {
+ $this->setWarning(
+ "Check that symbols such as \"+\" in the token are properly percent-encoded in the URL."
+ );
+ }
+
if ( $tokenObj->match( $token, $maxage ) ) {
$res['result'] = 'valid';
} elseif ( $maxage !== null && $tokenObj->match( $token ) ) {
$res['result'] = 'invalid';
}
- $ts = MediaWiki\Session\Token::getTimestamp( $token );
+ $ts = Token::getTimestamp( $token );
if ( $ts !== null ) {
$mwts = new MWTimestamp();
$mwts->timestamp->setTimestamp( $ts );
ApiBase::PARAM_DFLT => false,
ApiBase::PARAM_HELP_MSG => [
'api-pageset-param-converttitles',
- new DeferredStringifier(
- function ( IContextSource $context ) {
- return $context->getLanguage()
- ->commaList( LanguageConverter::$languagesWithVariants );
- },
- $this
- )
+ [ Message::listParam( LanguageConverter::$languagesWithVariants, 'text' ) ],
],
],
];
$matches = $search->searchText( $query );
}
}
- if ( is_null( $matches ) ) {
+
+ if ( $matches instanceof Status ) {
+ $status = $matches;
+ $matches = $status->getValue();
+ } else {
+ $status = null;
+ }
+
+ if ( $status ) {
+ if ( $status->isOK() ) {
+ $this->getMain()->getErrorFormatter()->addMessagesFromStatus(
+ $this->getModuleName(),
+ $status
+ );
+ } else {
+ $this->dieUsage( $status->getWikiText( false, false, 'en' ), 'search-error' );
+ }
+ } elseif ( is_null( $matches ) ) {
$this->dieUsage( "{$what} search is disabled", "search-{$what}-disabled" );
- } elseif ( $matches instanceof Status && !$matches->isGood() ) {
- $this->dieUsage( $matches->getWikiText( false, false, 'en' ), 'search-error' );
}
if ( $resultPageSet === null ) {
$pwhash = null;
- if ( $this->loginOnly ) {
- $pwhash = $this->getPasswordFactory()->newFromCiphertext( null );
- $expiry = null;
- // @codeCoverageIgnoreStart
- } elseif ( get_class( $req ) === PasswordAuthenticationRequest::class ) {
- // @codeCoverageIgnoreEnd
- $pwhash = $this->getPasswordFactory()->newFromPlaintext( $req->password );
- $expiry = $this->getNewPasswordExpiry( $username );
+ if ( get_class( $req ) === PasswordAuthenticationRequest::class ) {
+ if ( $this->loginOnly ) {
+ $pwhash = $this->getPasswordFactory()->newFromCiphertext( null );
+ $expiry = null;
+ } else {
+ $pwhash = $this->getPasswordFactory()->newFromPlaintext( $req->password );
+ $expiry = $this->getNewPasswordExpiry( $username );
+ }
}
if ( $pwhash ) {
return $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
} else {
return new MediaTransformError( 'thumbnail_error',
- $params['width'], 0, wfMessage( 'thumbnail-dest-create' )->text() );
+ $params['width'], 0, wfMessage( 'thumbnail-dest-create' ) );
}
}
--- /dev/null
+<?php
+
+namespace Wikimedia\Rdbms;
+
+use Database;
+use DBConnRef;
+use IDatabase;
+use InvalidArgumentException;
+use LoadBalancer;
+
+/**
+ * Database connection manager.
+ *
+ * This manages access to master and replica databases.
+ *
+ * @since 1.29
+ *
+ * @license GPL-2.0+
+ * @author Addshore
+ */
+class ConnectionManager {
+
+ /**
+ * @var LoadBalancer
+ */
+ private $loadBalancer;
+
+ /**
+ * The symbolic name of the target database, or false for the local wiki's database.
+ *
+ * @var string|false
+ */
+ private $domain;
+
+ /**
+ * @var string[]
+ */
+ private $groups = [];
+
+ /**
+ * @param LoadBalancer $loadBalancer
+ * @param string|bool $domain Optional logical DB name, defaults to current wiki.
+ * This follows the convention for database names used by $loadBalancer.
+ * @param string[] $groups see LoadBalancer::getConnection
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct( LoadBalancer $loadBalancer, $domain = false, array $groups = [] ) {
+ if ( !is_string( $domain ) && $domain !== false ) {
+ throw new InvalidArgumentException( '$dbName must be a string, or false.' );
+ }
+
+ $this->loadBalancer = $loadBalancer;
+ $this->domain = $domain;
+ $this->groups = $groups;
+ }
+
+ /**
+ * @param int $i
+ * @param string[]|null $groups
+ *
+ * @return Database
+ */
+ private function getConnection( $i, array $groups = null ) {
+ $groups = $groups === null ? $this->groups : $groups;
+ return $this->loadBalancer->getConnection( $i, $groups, $this->domain );
+ }
+
+ /**
+ * @param int $i
+ * @param string[]|null $groups
+ *
+ * @return DBConnRef
+ */
+ private function getConnectionRef( $i, array $groups = null ) {
+ $groups = $groups === null ? $this->groups : $groups;
+ return $this->loadBalancer->getConnectionRef( $i, $groups, $this->domain );
+ }
+
+ /**
+ * Returns a connection to the master DB, for updating. The connection should later be released
+ * by calling releaseConnection().
+ *
+ * @since 1.29
+ *
+ * @return Database
+ */
+ public function getWriteConnection() {
+ return $this->getConnection( DB_MASTER );
+ }
+
+ /**
+ * Returns a database connection for reading. The connection should later be released by
+ * calling releaseConnection().
+ *
+ * @since 1.29
+ *
+ * @param string[]|null $groups
+ *
+ * @return Database
+ */
+ public function getReadConnection( array $groups = null ) {
+ $groups = $groups === null ? $this->groups : $groups;
+ return $this->getConnection( DB_REPLICA, $groups );
+ }
+
+ /**
+ * @since 1.29
+ *
+ * @param IDatabase $db
+ */
+ public function releaseConnection( IDatabase $db ) {
+ $this->loadBalancer->reuseConnection( $db );
+ }
+
+ /**
+ * Returns a connection ref to the master DB, for updating.
+ *
+ * @since 1.29
+ *
+ * @return DBConnRef
+ */
+ public function getWriteConnectionRef() {
+ return $this->getConnectionRef( DB_MASTER );
+ }
+
+ /**
+ * Returns a database connection ref for reading.
+ *
+ * @since 1.29
+ *
+ * @param string[]|null $groups
+ *
+ * @return DBConnRef
+ */
+ public function getReadConnectionRef( array $groups = null ) {
+ $groups = $groups === null ? $this->groups : $groups;
+ return $this->getConnectionRef( DB_REPLICA, $groups );
+ }
+
+ /**
+ * Begins an atomic section and returns a database connection to the master DB, for updating.
+ *
+ * @since 1.29
+ *
+ * @param string $fname
+ *
+ * @return Database
+ */
+ public function beginAtomicSection( $fname ) {
+ $db = $this->getWriteConnection();
+ $db->startAtomic( $fname );
+
+ return $db;
+ }
+
+ /**
+ * @since 1.29
+ *
+ * @param IDatabase $db
+ * @param string $fname
+ */
+ public function commitAtomicSection( IDatabase $db, $fname ) {
+ $db->endAtomic( $fname );
+ $this->releaseConnection( $db );
+ }
+
+ /**
+ * @since 1.29
+ *
+ * @param IDatabase $db
+ * @param string $fname
+ */
+ public function rollbackAtomicSection( IDatabase $db, $fname ) {
+ // FIXME: there does not seem to be a clean way to roll back an atomic section?!
+ $db->rollback( $fname, 'flush' );
+ $this->releaseConnection( $db );
+ }
+
+}
--- /dev/null
+<?php
+
+namespace Wikimedia\Rdbms;
+
+use Database;
+use DBConnRef;
+
+/**
+ * Database connection manager.
+ *
+ * This manages access to master and slave databases. It also manages state that indicates whether
+ * the slave databases are possibly outdated after a write operation, and thus the master database
+ * should be used for subsequent read operations.
+ *
+ * @note: Services that access overlapping sets of database tables, or interact with logically
+ * related sets of data in the database, should share a SessionConsistentConnectionManager.
+ * Services accessing unrelated sets of information may prefer to not share a
+ * SessionConsistentConnectionManager, so they can still perform read operations against slave
+ * databases after a (unrelated, per the assumption) write operation to the master database.
+ * Generally, sharing a SessionConsistentConnectionManager improves consistency (by avoiding race
+ * conditions due to replication lag), but can reduce performance (by directing more read
+ * operations to the master database server).
+ *
+ * @since 1.29
+ *
+ * @license GPL-2.0+
+ * @author Daniel Kinzler
+ * @author Addshore
+ */
+class SessionConsistentConnectionManager extends ConnectionManager {
+
+ /**
+ * @var bool
+ */
+ private $forceWriteConnection = false;
+
+ /**
+ * Forces all future calls to getReadConnection() to return a write connection.
+ * Use this before performing read operations that are critical for a future update.
+ * Calling beginAtomicSection() implies a call to prepareForUpdates().
+ *
+ * @since 1.29
+ */
+ public function prepareForUpdates() {
+ $this->forceWriteConnection = true;
+ }
+
+ /**
+ * @since 1.29
+ *
+ * @param string[]|null $groups
+ *
+ * @return Database
+ */
+ public function getReadConnection( array $groups = null ) {
+ if ( $this->forceWriteConnection ) {
+ return parent::getWriteConnection();
+ }
+
+ return parent::getReadConnection( $groups );
+ }
+
+ /**
+ * @since 1.29
+ *
+ * @return Database
+ */
+ public function getWriteConnection() {
+ $this->prepareForUpdates();
+ return parent::getWriteConnection();
+ }
+
+ /**
+ * @since 1.29
+ *
+ * @param string[]|null $groups
+ *
+ * @return DBConnRef
+ */
+ public function getReadConnectionRef( array $groups = null ) {
+ if ( $this->forceWriteConnection ) {
+ return parent::getWriteConnectionRef();
+ }
+
+ return parent::getReadConnectionRef( $groups );
+ }
+
+ /**
+ * @since 1.29
+ *
+ * @return DBConnRef
+ */
+ public function getWriteConnectionRef() {
+ $this->prepareForUpdates();
+ return parent::getWriteConnectionRef();
+ }
+
+ /**
+ * Begins an atomic section and returns a database connection to the master DB, for updating.
+ *
+ * @since 1.29
+ *
+ * @note: This causes all future calls to getReadConnection() to return a connection
+ * to the master DB, even after commitAtomicSection() or rollbackAtomicSection() have
+ * been called.
+ *
+ * @param string $fname
+ *
+ * @return Database
+ */
+ public function beginAtomicSection( $fname ) {
+ // Once we have written to master, do not read from replica.
+ $this->prepareForUpdates();
+
+ return parent::beginAtomicSection( $fname );
+ }
+
+}
* @param array $params Rotate parameters.
* 'rotation' clockwise rotation in degrees, allowed are multiples of 90
* @since 1.21
- * @return bool
+ * @return bool|MediaTransformError
*/
public function rotate( $file, $params ) {
global $wgImageMagickConvertCommand;
'thumbnail_error',
$width,
$height,
- wfMessage( 'thumbnail_dest_directory' )->text()
+ wfMessage( 'thumbnail_dest_directory' )
);
}
return new MediaTransformError( 'thumbnail_error',
$params['width'], $params['height'],
- wfMessage( 'filemissing' )->text()
+ wfMessage( 'filemissing' )
);
}
* @param array $params Rotate parameters.
* 'rotation' clockwise rotation in degrees, allowed are multiples of 90
* @since 1.21
- * @return bool
+ * @return bool|MediaTransformError
*/
public function rotate( $file, $params ) {
global $wgJpegTran;
* @ingroup Media
*/
class MediaTransformError extends MediaTransformOutput {
- /** @var string HTML formatted version of the error */
- private $htmlMsg;
-
- /** @var string Plain text formatted version of the error */
- private $textMsg;
+ /** @var Message */
+ private $msg;
function __construct( $msg, $width, $height /*, ... */ ) {
$args = array_slice( func_get_args(), 3 );
- $htmlArgs = array_map( 'htmlspecialchars', $args );
- $htmlArgs = array_map( 'nl2br', $htmlArgs );
-
- $this->htmlMsg = wfMessage( $msg )->rawParams( $htmlArgs )->escaped();
- $this->textMsg = wfMessage( $msg )->rawParams( $htmlArgs )->text();
+ $this->msg = wfMessage( $msg )->params( $args );
$this->width = intval( $width );
$this->height = intval( $height );
$this->url = false;
function toHtml( $options = [] ) {
return "<div class=\"MediaTransformError\" style=\"" .
"width: {$this->width}px; height: {$this->height}px; display:inline-block;\">" .
- $this->htmlMsg .
+ $this->getHtmlMsg() .
"</div>";
}
function toText() {
- return $this->textMsg;
+ return $this->msg->text();
}
function getHtmlMsg() {
- return $this->htmlMsg;
+ return $this->msg->escaped();
+ }
+
+ function getMsg() {
+ return $this->msg;
}
function isError() {
parent::__construct( 'thumbnail_error',
max( isset( $params['width'] ) ? $params['width'] : 0, 120 ),
max( isset( $params['height'] ) ? $params['height'] : 0, 120 ),
- wfMessage( 'thumbnail_invalid_params' )->text() );
+ wfMessage( 'thumbnail_invalid_params' )
+ );
}
function getHttpStatusCode() {
class TransformTooBigImageAreaError extends MediaTransformError {
function __construct( $params, $maxImageArea ) {
$msg = wfMessage( 'thumbnail_toobigimagearea' );
+ $msg->rawParams(
+ $msg->getLanguage()->formatComputingNumbers( $maxImageArea, 1000, "size-$1pixel" )
+ );
parent::__construct( 'thumbnail_error',
max( isset( $params['width'] ) ? $params['width'] : 0, 120 ),
max( isset( $params['height'] ) ? $params['height'] : 0, 120 ),
- $msg->rawParams(
- $msg->getLanguage()->formatComputingNumbers(
- $maxImageArea, 1000, "size-$1pixel" )
- )->text()
- );
+ $msg
+ );
}
function getHttpStatusCode() {
$metadata = $this->unpackMetadata( $image->getMetadata() );
if ( isset( $metadata['error'] ) ) { // sanity check
- $err = wfMessage( 'svg-long-error', $metadata['error']['message'] )->text();
+ $err = wfMessage( 'svg-long-error', $metadata['error']['message'] );
return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
}
if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight,
- wfMessage( 'thumbnail_dest_directory' )->text() );
+ wfMessage( 'thumbnail_dest_directory' ) );
}
$srcPath = $image->getLocalRefPath();
return new MediaTransformError( 'thumbnail_error',
$params['width'], $params['height'],
- wfMessage( 'filemissing' )->text()
+ wfMessage( 'filemissing' )
);
}
wfHostname(), $lnPath, $srcPath ) );
return new MediaTransformError( 'thumbnail_error',
$params['width'], $params['height'],
- wfMessage( 'thumbnail-temp-create' )->text()
+ wfMessage( 'thumbnail-temp-create' )
);
}
return new MediaTransformError( 'thumbnail_error',
$scalerParams['clientWidth'], $scalerParams['clientHeight'],
- wfMessage( 'filemissing' )->text()
+ wfMessage( 'filemissing' )
);
}
# Thumbnail was zero-byte and had to be removed
return new MediaTransformError( 'thumbnail_error',
$scalerParams['clientWidth'], $scalerParams['clientHeight'],
- wfMessage( 'unknown-error' )->text()
+ wfMessage( 'unknown-error' )
);
} elseif ( $mto ) {
return $mto;
* @param array $params Rotate parameters.
* 'rotation' clockwise rotation in degrees, allowed are multiples of 90
* @since 1.24 Is non-static. From 1.21 it was static
- * @return bool
+ * @return bool|MediaTransformError
*/
public function rotate( $file, $params ) {
return new MediaTransformError( 'thumbnail_error', 0, 0,
$textStatus = null;
if ( $textMatches instanceof Status ) {
$textStatus = $textMatches;
- $textMatches = null;
+ $textMatches = $textStatus->getValue();
}
// did you mean... suggestions
$didYouMeanHtml = '';
- if ( $showSuggestion && $textMatches && !$textStatus ) {
+ if ( $showSuggestion && $textMatches ) {
if ( $textMatches->hasRewrittenQuery() ) {
$didYouMeanHtml = $this->getDidYouMeanRewrittenHtml( $term, $textMatches );
} elseif ( $textMatches->hasSuggestion() ) {
$out->addHTML( "<div class='searchresults'>" );
+ $hasErrors = $textStatus && $textStatus->getErrors();
+ if ( $hasErrors ) {
+ list( $error, $warning ) = $textStatus->splitByErrorType();
+ if ( $error->getErrors() ) {
+ $out->addHTML( Html::rawElement(
+ 'div',
+ [ 'class' => 'errorbox' ],
+ $error->getHTML( 'search-error' )
+ ) );
+ }
+ if ( $warning->getErrors() ) {
+ $out->addHTML( Html::rawElement(
+ 'div',
+ [ 'class' => 'warningbox' ],
+ $warning->getHTML( 'search-warning' )
+ ) );
+ }
+ }
+
// prev/next links
$prevnext = null;
if ( $num || $this->offset ) {
}
$titleMatches->free();
}
- if ( $textMatches && !$textStatus ) {
+
+ if ( $textMatches ) {
// output appropriate heading
if ( $numTextMatches > 0 && $numTitleMatches > 0 ) {
$out->addHTML( '<div class="mw-search-visualclear"></div>' );
$hasOtherResults = $textMatches &&
$textMatches->hasInterwikiResults( SearchResultSet::INLINE_RESULTS );
- if ( $num === 0 ) {
- if ( $textStatus ) {
- $out->addHTML( '<div class="error">' .
- $textStatus->getMessage( 'search-error' ) . '</div>' );
- } else {
- if ( !$this->offset ) {
- // If we have an offset the create link was rendered earlier in this function.
- // This class needs a good de-spaghettification, but for now this will
- // do the job.
- $this->showCreateLink( $title, $num, $titleMatches, $textMatches );
- }
- $out->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>",
- [ $hasOtherResults ? 'search-nonefound-thiswiki' : 'search-nonefound',
- wfEscapeWikiText( $term )
- ] );
+ // If we have no results and we have not already displayed an error message
+ if ( $num === 0 && !$hasErrors ) {
+ if ( !$this->offset ) {
+ // If we have an offset the create link was rendered earlier in this function.
+ // This class needs a good de-spaghettification, but for now this will
+ // do the job.
+ $this->showCreateLink( $title, $num, $titleMatches, $textMatches );
}
+ $out->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>", [
+ $hasOtherResults ? 'search-nonefound-thiswiki' : 'search-nonefound',
+ wfEscapeWikiText( $term )
+ ] );
}
if ( $hasOtherResults ) {
* @param bool $writing
* @return Status
*/
- public function fetchUser( $username, $writing ) {
+ public function fetchUser( $username, $writing = true ) {
$parts = explode( $this->getConfig()->get( 'UserrightsInterwikiDelimiter' ), $username );
if ( count( $parts ) < 2 ) {
$name = trim( $username );
"searchdisabled": "{{SITENAME}} search is disabled.\nYou can search via Google in the meantime.\nNote that their indexes of {{SITENAME}} content may be out of date.",
"googlesearch": "<form method=\"get\" action=\"//www.google.com/search\" id=\"googlesearch\">\n\t<input type=\"hidden\" name=\"domains\" value=\"{{SERVER}}\" />\n\t<input type=\"hidden\" name=\"num\" value=\"50\" />\n\t<input type=\"hidden\" name=\"ie\" value=\"$2\" />\n\t<input type=\"hidden\" name=\"oe\" value=\"$2\" />\n\n\t<input type=\"text\" name=\"q\" size=\"31\" maxlength=\"255\" value=\"$1\" />\n\t<input type=\"submit\" name=\"btnG\" value=\"$3\" />\n <div>\n\t<input type=\"radio\" name=\"sitesearch\" id=\"gwiki\" value=\"{{SERVER}}\" checked=\"checked\" /><label for=\"gwiki\">{{SITENAME}}</label>\n\t<input type=\"radio\" name=\"sitesearch\" id=\"gWWW\" value=\"\" /><label for=\"gWWW\">WWW</label>\n </div>\n</form>",
"search-error": "An error has occurred while searching: $1",
+ "search-warning": "A warning has occured while searching: $1",
"opensearch-desc": "{{SITENAME}} ({{CONTENTLANGUAGE}})",
"preferences": "Preferences",
"preferences-summary": "",
"search-external": "Legend of the fieldset for the input form when the internal search is disabled. Inside the fieldset [[MediaWiki:Searchdisabled]] and [[MediaWiki:Googlesearch]] is shown.",
"searchdisabled": "{{doc-singularthey}}\nIn this sentence, \"their indexes\" refers to \"Google's indexes\".\n\nShown on [[Special:Search]] when the internal search is disabled.",
"googlesearch": "{{notranslate}}\nShown when [[mw:Manual:$wgDisableTextSearch|$wgDisableTextSearch]] is set to true and no [[mw:Manual:$wgSearchForwardUrl|$wgSearchForwardUrl]] is set.\n\nParameters:\n* $1 - the search term\n* $2 - \"UTF-8\" (hard-coded)\n* $3 - the message {{msg-mw|Searchbutton}}",
- "search-error": "Shown when an error has occurred when performing a search. Parameters:\n* $1 - the localized error that was returned",
+ "search-error": "Shown when an error has occurred when performing a search. Parameters:\n* $1 - the localized error that was returned.",
+ "search-warning": "Shown when a warning has occured when performing a search. Parameters:\n* $1 - the localized warning that was returned.",
"opensearch-desc": "{{ignored}}Link description of the [www.opensearch.org/ OpenSearch] link in the HTML head of pages.",
"preferences": "Title of the [[Special:Preferences]] page.\n{{Identical|Preferences}}",
"preferences-summary": "{{doc-specialpagesummary|preferences}}",
"ooui-dialog-process-dismiss": "Απόρριψη",
"ooui-dialog-process-retry": "Δοκιμάστε ξανά",
"ooui-dialog-process-continue": "Συνέχεια",
+ "ooui-selectfile-button-select": "Επιλέξτε ένα αρχείο",
"ooui-selectfile-not-supported": "Επιλογή αρχείου δεν υποστηρίζεται",
"ooui-selectfile-placeholder": "Κανένα αρχείο δεν είναι επιλεγμένο",
"ooui-selectfile-dragdrop-placeholder": "Σύρετε το αρχείο εδώ"
/*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.1
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-11-09T00:52:37Z
+ * Date: 2016-11-29T22:57:37Z
*/
( function ( OO ) {
/*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.1
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-11-09T00:52:42Z
+ * Date: 2016-11-29T22:57:42Z
*/
.oo-ui-element-hidden {
display: none !important;
/*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.1
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-11-09T00:52:42Z
+ * Date: 2016-11-29T22:57:42Z
*/
.oo-ui-element-hidden {
display: none !important;
box-shadow: none;
}
.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
- color: #c33;
+ color: #d33;
}
.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:hover > .oo-ui-labelElement-label {
- color: #e53939;
+ color: #ff4242;
}
.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:active > .oo-ui-labelElement-label,
.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
- color: #873636;
+ color: #b32424;
box-shadow: none;
}
.oo-ui-buttonElement-frameless.oo-ui-widget-enabled[class*='oo-ui-flaggedElement'] > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon,
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
- background-color: #d9d9d9;
+ background-color: #c8ccd1;
color: #000;
border-color: #72777d;
}
box-shadow: inset 0 0 0 1px #36c;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button {
- color: #c33;
+ color: #d33;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:hover {
background-color: #fff;
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:active:focus,
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button,
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
- background-color: #fbf4f4;
- color: #873636;
- border-color: #873636;
+ background-color: #ffffff;
+ color: #b32424;
+ border-color: #b32424;
box-shadow: none;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:focus {
- border-color: #c33;
- box-shadow: inset 0 0 0 1px #c33;
+ border-color: #d33;
+ box-shadow: inset 0 0 0 1px #d33;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button {
color: #fff;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button {
color: #fff;
- background-color: #c33;
- border-color: #c33;
+ background-color: #d33;
+ border-color: #d33;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:hover {
- background-color: #e53939;
- border-color: #e53939;
+ background-color: #ff4242;
+ border-color: #ff4242;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:active,
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:active:focus,
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button,
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
color: #fff;
- background-color: #873636;
- border-color: #873636;
+ background-color: #b32424;
+ border-color: #b32424;
box-shadow: none;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button:focus {
- border-color: #c33;
- box-shadow: inset 0 0 0 1px #c33, inset 0 0 0 2px #fff;
+ border-color: #d33;
+ box-shadow: inset 0 0 0 1px #d33, inset 0 0 0 2px #fff;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon,
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
}
.oo-ui-fieldLayout {
display: block;
- margin-bottom: 1em;
+ margin-top: 1.640625em;
}
.oo-ui-fieldLayout:before,
.oo-ui-fieldLayout:after {
padding: 0.5em 0.75em;
line-height: 1.5;
}
-.oo-ui-fieldLayout:last-child {
- margin-bottom: 0;
+.oo-ui-fieldLayout.oo-ui-labelElement,
+.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline {
+ margin-top: 1.171875em;
}
-.oo-ui-fieldLayout.oo-ui-fieldLayout-align-left.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label,
-.oo-ui-fieldLayout.oo-ui-fieldLayout-align-right.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
- padding-top: 0.5em;
- margin-right: 5%;
- width: 35%;
+.oo-ui-fieldLayout:first-child,
+.oo-ui-fieldLayout.oo-ui-labelElement:first-child,
+.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline:first-child {
+ margin-top: 0;
}
-.oo-ui-fieldLayout.oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field,
-.oo-ui-fieldLayout.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field {
- width: 60%;
+.oo-ui-fieldLayout.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
+ padding-bottom: 0.3125em;
}
-.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline {
- margin-bottom: 1.25em;
+.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-inline > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
+ padding: 0.3125em 0.46875em;
+}
+.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label,
+.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
+ width: 35%;
+ margin-right: 5%;
+ padding-top: 0.3125em;
}
-.oo-ui-fieldLayout.oo-ui-fieldLayout-align-inline.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
- padding: 0.25em 0.25em 0.25em 0.5em;
+.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field,
+.oo-ui-fieldLayout.oo-ui-labelElement.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field {
+ width: 60%;
}
-.oo-ui-fieldLayout.oo-ui-fieldLayout-align-top.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
- padding-top: 0.25em;
- padding-bottom: 0.5em;
+.oo-ui-fieldLayout-disabled > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
+ color: #72777d;
}
.oo-ui-fieldLayout > .oo-ui-popupButtonWidget {
margin-right: 0;
.oo-ui-fieldLayout > .oo-ui-popupButtonWidget:last-child {
margin-right: 0;
}
-.oo-ui-fieldLayout-disabled > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
- color: #72777d;
-}
.oo-ui-fieldLayout-messages {
list-style: none none;
margin: 0.25em 0 0 0.25em;
}
.oo-ui-fieldLayout-messages .oo-ui-iconWidget {
display: table-cell;
- border-right: 0.5em solid transparent;
}
.oo-ui-fieldLayout-messages .oo-ui-labelWidget {
display: table-cell;
- padding: 0.1em 0;
+ padding: 0.1em 0 0.1em 0.3125em;
line-height: 1.5;
vertical-align: middle;
}
margin-top: 2em;
}
.oo-ui-fieldsetLayout.oo-ui-labelElement > .oo-ui-labelElement-label {
- margin-bottom: 0.5em;
+ margin-bottom: 0.56818em;
font-size: 1.1em;
font-weight: bold;
}
background-color: transparent;
}
.oo-ui-radioOptionWidget.oo-ui-labelElement .oo-ui-labelElement-label {
- padding: 0.25em 0.25em 0.25em 0.5em;
+ padding: 0.25em 0.25em 0.25em 0.46875em;
}
.oo-ui-radioOptionWidget .oo-ui-radioInputWidget {
margin-right: 0;
}
.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:hover {
background-color: #fff;
+ color: #444;
border-color: #a2a9b1;
}
.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:hover .oo-ui-iconElement-icon,
.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:hover .oo-ui-indicatorElement-indicator {
opacity: 0.73;
}
+.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:active {
+ color: #000;
+ border-color: #72777d;
+}
.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:focus {
border-color: #36c;
outline: 0;
vertical-align: middle;
}
.oo-ui-checkboxMultioptionWidget.oo-ui-labelElement .oo-ui-labelElement-label {
- padding: 0.25em 0.25em 0.25em 0.5em;
+ padding: 0.25em 0.25em 0.25em 0.46875em;
}
.oo-ui-checkboxMultioptionWidget .oo-ui-checkboxInputWidget {
margin-right: 0;
/*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.1
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-11-09T00:52:37Z
+ * Date: 2016-11-29T22:57:37Z
*/
( function ( OO ) {
}
return message;
};
-} )();
+}() );
/**
* Package a message and arguments for deferred resolution.
OO.ui.PopupWidget.prototype.onMouseDown = function ( e ) {
if (
this.isVisible() &&
- !$.contains( this.$element[ 0 ], e.target ) &&
- ( !this.$autoCloseIgnore || !this.$autoCloseIgnore.has( e.target ).length )
+ !OO.ui.contains( this.$element.add( this.$autoCloseIgnore ).get(), e.target, true )
) {
this.toggle( false );
}
this.popup = new OO.ui.PopupWidget( $.extend(
{ autoClose: true },
config.popup,
- { $autoCloseIgnore: this.$element }
+ { $autoCloseIgnore: this.$element.add( config.popup && config.popup.$autoCloseIgnore ) }
) );
};
* @throws {Error} An error is thrown if no widget is specified
*/
OO.ui.FieldLayout = function OoUiFieldLayout( fieldWidget, config ) {
- var hasInputWidget, div;
+ var hasInputWidget, $div;
// Allow passing positional parameters inside the config object
if ( OO.isPlainObject( fieldWidget ) && config === undefined ) {
icon: 'info'
} );
- div = $( '<div>' );
+ $div = $( '<div>' );
if ( config.help instanceof OO.ui.HtmlSnippet ) {
- div.html( config.help.toString() );
+ $div.html( config.help.toString() );
} else {
- div.text( config.help );
+ $div.text( config.help );
}
this.popupButtonWidget.getPopup().$body.append(
- div.addClass( 'oo-ui-fieldLayout-help-content' )
+ $div.addClass( 'oo-ui-fieldLayout-help-content' )
);
this.$help = this.popupButtonWidget.$element;
} else {
* @constructor
* @param {Object} [config] Configuration options
* @cfg {OO.ui.FieldLayout[]} [items] An array of fields to add to the fieldset. See OO.ui.FieldLayout for more information about fields.
+ * @cfg {string|OO.ui.HtmlSnippet} [help] Help text. When help text is specified, a "help" icon will appear
+ * in the upper-right corner of the rendered field; clicking it will display the text in a popup.
+ * For important messages, you are advised to use `notices`, as they are always shown.
*/
OO.ui.FieldsetLayout = function OoUiFieldsetLayout( config ) {
+ var $div;
+
// Configuration initialization
config = config || {};
icon: 'info'
} );
+ $div = $( '<div>' );
+ if ( config.help instanceof OO.ui.HtmlSnippet ) {
+ $div.html( config.help.toString() );
+ } else {
+ $div.text( config.help );
+ }
this.popupButtonWidget.getPopup().$body.append(
- $( '<div>' )
- .text( config.help )
- .addClass( 'oo-ui-fieldsetLayout-help-content' )
+ $div.addClass( 'oo-ui-fieldsetLayout-help-content' )
);
this.$help = this.popupButtonWidget.$element;
} else {
/*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.1
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-11-09T00:52:37Z
+ * Date: 2016-11-29T22:57:37Z
*/
( function ( OO ) {
/*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.1
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-11-09T00:52:42Z
+ * Date: 2016-11-29T22:57:42Z
*/
.oo-ui-popupTool .oo-ui-popupWidget-popup,
.oo-ui-popupTool .oo-ui-popupWidget-anchor {
/*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.1
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-11-09T00:52:42Z
+ * Date: 2016-11-29T22:57:42Z
*/
.oo-ui-tool.oo-ui-widget-enabled {
-webkit-transition: background-color 100ms;
/*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.1
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-11-09T00:52:37Z
+ * Date: 2016-11-29T22:57:37Z
*/
( function ( OO ) {
/*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.1
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-11-09T00:52:42Z
+ * Date: 2016-11-29T22:57:42Z
*/
.oo-ui-draggableElement-handle,
.oo-ui-draggableElement-handle.oo-ui-widget {
/*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.1
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-11-09T00:52:42Z
+ * Date: 2016-11-29T22:57:42Z
*/
.oo-ui-draggableElement-handle,
.oo-ui-draggableElement-handle.oo-ui-widget {
.oo-ui-capsuleMultiselectWidget-handle > .oo-ui-iconElement-icon {
position: absolute;
}
+.oo-ui-capsuleMultiselectWidget-handle > .oo-ui-capsuleMultiselectWidget-content :-moz-placeholder {
+ color: #72777d;
+ opacity: 1;
+}
+.oo-ui-capsuleMultiselectWidget-handle > .oo-ui-capsuleMultiselectWidget-content ::-moz-placeholder {
+ color: #72777d;
+ opacity: 1;
+}
+.oo-ui-capsuleMultiselectWidget-handle > .oo-ui-capsuleMultiselectWidget-content :-ms-input-placeholder {
+ color: #72777d;
+}
+.oo-ui-capsuleMultiselectWidget-handle > .oo-ui-capsuleMultiselectWidget-content ::-webkit-input-placeholder {
+ color: #72777d;
+}
+.oo-ui-capsuleMultiselectWidget-handle > .oo-ui-capsuleMultiselectWidget-content :placeholder-shown {
+ color: #72777d;
+}
.oo-ui-capsuleMultiselectWidget-handle > .oo-ui-capsuleMultiselectWidget-content > input {
border: 0;
line-height: 1.675;
/*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.1
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-11-09T00:52:37Z
+ * Date: 2016-11-29T22:57:37Z
*/
( function ( OO ) {
setTimeout( function () {
if (
widget.isVisible() &&
- !OO.ui.contains( widget.$element[ 0 ], document.activeElement, true ) &&
- ( !widget.$autoCloseIgnore || !widget.$autoCloseIgnore.has( document.activeElement ).length )
+ !OO.ui.contains( widget.$element.add( widget.$autoCloseIgnore ).get(), document.activeElement, true )
) {
widget.toggle( false );
}
/*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.1
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-11-09T00:52:42Z
+ * Date: 2016-11-29T22:57:42Z
*/
.oo-ui-actionWidget.oo-ui-pendingElement-pending {
background-image: /* @embed */ url(themes/apex/images/textures/pending.gif);
/*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.1
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-11-09T00:52:42Z
+ * Date: 2016-11-29T22:57:42Z
*/
.oo-ui-window {
background: transparent;
/*!
- * OOjs UI v0.18.0
+ * OOjs UI v0.18.1
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-11-09T00:52:37Z
+ * Date: 2016-11-29T22:57:37Z
*/
( function ( OO ) {
*
* @param {OO.ui.Window|string} win Window object or symbolic name of window to open
* @param {Object} [data] Window opening data
- * @param {jQuery} [data.$returnFocusTo] Element to which the window will return focus when closed.
+ * @param {jQuery|null} [data.$returnFocusTo] Element to which the window will return focus when closed.
+ * Defaults the current activeElement. If set to null, focus isn't changed on close.
* @return {jQuery.Promise} An `opening` promise resolved when the window is done opening.
* See {@link #event-opening 'opening' event} for more information about `opening` promises.
* @fires opening
manager.toggleGlobalEvents( false );
manager.toggleAriaIsolation( false );
}
- manager.$returnFocusTo[ 0 ].focus();
+ if ( manager.$returnFocusTo && manager.$returnFocusTo.length ) {
+ manager.$returnFocusTo[ 0 ].focus();
+ }
manager.closing = null;
manager.currentWindow = null;
closing.resolve( data );
"color": "#36c"
},
"destructive": {
- "color": "#c33"
+ "color": "#d33"
},
"warning": {
"color": "#ff5d00"
"color": "#36c"
},
"destructive": {
- "color": "#c33"
+ "color": "#d33"
},
"warning": {
"color": "#ff5d00"
"color": "#36c"
},
"destructive": {
- "color": "#c33"
+ "color": "#d33"
},
"warning": {
"color": "#ff5d00"
"color": "#36c"
},
"destructive": {
- "color": "#c33"
+ "color": "#d33"
},
"warning": {
"color": "#ff5d00"
"color": "#36c"
},
"destructive": {
- "color": "#c33"
+ "color": "#d33"
},
"warning": {
"color": "#ff5d00"
"color": "#36c"
},
"destructive": {
- "color": "#c33"
+ "color": "#d33"
},
"warning": {
"color": "#ff5d00"
"color": "#36c"
},
"destructive": {
- "color": "#c33"
+ "color": "#d33"
},
"warning": {
"color": "#ff5d00"
"color": "#36c"
},
"destructive": {
- "color": "#c33"
+ "color": "#d33"
},
"warning": {
"color": "#ff5d00"
"color": "#36c"
},
"destructive": {
- "color": "#c33"
+ "color": "#d33"
},
"warning": {
"color": "#ff5d00"
"color": "#36c"
},
"destructive": {
- "color": "#c33"
+ "color": "#d33"
},
"warning": {
"color": "#ff5d00"
"color": "#36c"
},
"destructive": {
- "color": "#c33"
+ "color": "#d33"
},
"warning": {
"color": "#ff5d00"
"color": "#36c"
},
"destructive": {
- "color": "#c33"
+ "color": "#d33"
},
"warning": {
"color": "#ff5d00"
"color": "#36c"
},
"destructive": {
- "color": "#c33"
+ "color": "#d33"
},
"warning": {
"color": "#ff5d00"
"color": "#36c"
},
"destructive": {
- "color": "#c33"
+ "color": "#d33"
},
"warning": {
"color": "#ff5d00"
"color": "#36c"
},
"destructive": {
- "color": "#c33"
+ "color": "#d33"
},
"warning": {
"color": "#ff5d00"
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d33 }</style>
<path d="M12 4c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm5 9H7v-2h10v2z"/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d33 }</style>
<g id="cancel">
<path id="circle-with-strike" d="M12 5.022a6.98 6.98 0 0 0-.003 13.956 6.98 6.98 0 0 0-.002-13.956zM6.885 12c0-1.092.572-3.25.93-2.93l7.113 7.114c.487.525-1.838.93-2.93.93A5.113 5.113 0 0 1 6.884 12zm9.298 2.93L9.07 7.815c-.445-.483 1.837-.93 2.93-.93a5.112 5.112 0 0 1 5.114 5.113c0 1.092-.364 3.542-.93 2.93z"/>
</g>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d33 }</style>
<path d="M17 7.5L9.5 15 6 11.5 4.5 13l5 5L20 7.5c-.706-.706-2.294-.706-3 0z" id="check"/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d33 }</style>
<path d="M15 8s0-3-2.5-3S10 8 10 8v1h5zm2 0v1h2v10H9c-1.7 0-3-1.3-3-3V9h2V8s0-5 4.5-5S17 8 17 8z"/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d33 }</style>
<path d="M10 8s0-3 2.5-3S15 8 15 8v1h-5zM8 8v1H6v10h10c1.7 0 3-1.3 3-3V9h-2V8s0-5-4.5-5S8 8 8 8z"/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d33 }</style>
<path d="M18.748 11.717a1 1 0 0 1 0 1.414l-4.95 4.95a1 1 0 0 1-1.413 0l-6.01-6.01c-.39-.382-.707-1.15-.707-1.7V6c0-.55.45-1 1-1h4.363c.55 0 1.32.318 1.71.707l6.01 6.01zM8.104 7.457a1.477 1.477 0 0 0 0 2.092 1.49 1.49 0 0 0 2.094 0 1.49 1.49 0 0 0 0-2.1 1.484 1.484 0 0 0-2.094 0z" id="tag"/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d33 }</style>
<path d="M6 8c0-1.1.9-2 2-2h2l1-1h2l1 1h2c1.1 0 2 .9 2 2H6zm1 1h10l-1 11H8z"/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d33 }</style>
<path d="M12 9V7s0-5-4.5-5S3 7 3 7h2s0-3 2.5-3S10 7 10 7v2H7v7c0 1.7 1.3 3 3 3h10V9z"/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #d33 }</style>
<path d="M11 9V7s0-5 4.5-5S20 7 20 7h-2s0-3-2.5-3S13 7 13 7v2h3v7c0 1.7-1.3 3-3 3H3V9z"/>
</svg>
"color": "#36c"
},
"destructive": {
- "color": "#c33"
+ "color": "#d33"
},
"warning": {
"color": "#ff5d00"
);
}
+ public static function provideListParam() {
+ $lang = Language::factory( 'de' );
+ $msg1 = new Message( 'mainpage', [], $lang );
+ $msg2 = new RawMessage( "''link''", [], $lang );
+
+ return [
+ 'Simple comma list' => [
+ [ 'a', 'b', 'c' ],
+ 'comma',
+ 'text',
+ 'a, b, c'
+ ],
+
+ 'Simple semicolon list' => [
+ [ 'a', 'b', 'c' ],
+ 'semicolon',
+ 'text',
+ 'a; b; c'
+ ],
+
+ 'Simple pipe list' => [
+ [ 'a', 'b', 'c' ],
+ 'pipe',
+ 'text',
+ 'a | b | c'
+ ],
+
+ 'Simple text list' => [
+ [ 'a', 'b', 'c' ],
+ 'text',
+ 'text',
+ 'a, b and c'
+ ],
+
+ 'Empty list' => [
+ [],
+ 'comma',
+ 'text',
+ ''
+ ],
+
+ 'List with all "before" params, ->text()' => [
+ [ "''link''", Message::numParam( 12345678 ) ],
+ 'semicolon',
+ 'text',
+ '\'\'link\'\'; 12,345,678'
+ ],
+
+ 'List with all "before" params, ->parse()' => [
+ [ "''link''", Message::numParam( 12345678 ) ],
+ 'semicolon',
+ 'parse',
+ '<i>link</i>; 12,345,678'
+ ],
+
+ 'List with all "after" params, ->text()' => [
+ [ $msg1, $msg2, Message::rawParam( '[[foo]]' ) ],
+ 'semicolon',
+ 'text',
+ 'Main Page; \'\'link\'\'; [[foo]]'
+ ],
+
+ 'List with all "after" params, ->parse()' => [
+ [ $msg1, $msg2, Message::rawParam( '[[foo]]' ) ],
+ 'semicolon',
+ 'parse',
+ 'Main Page; <i>link</i>; [[foo]]'
+ ],
+
+ 'List with both "before" and "after" params, ->text()' => [
+ [ $msg1, $msg2, Message::rawParam( '[[foo]]' ), "''link''", Message::numParam( 12345678 ) ],
+ 'semicolon',
+ 'text',
+ 'Main Page; \'\'link\'\'; [[foo]]; \'\'link\'\'; 12,345,678'
+ ],
+
+ 'List with both "before" and "after" params, ->parse()' => [
+ [ $msg1, $msg2, Message::rawParam( '[[foo]]' ), "''link''", Message::numParam( 12345678 ) ],
+ 'semicolon',
+ 'parse',
+ 'Main Page; <i>link</i>; [[foo]]; <i>link</i>; 12,345,678'
+ ],
+ ];
+ }
+
+ /**
+ * @covers Message::listParam
+ * @covers Message::extractParam
+ * @covers Message::formatListParam
+ * @dataProvider provideListParam
+ */
+ public function testListParam( $list, $type, $format, $expect ) {
+ $lang = Language::factory( 'en' );
+
+ $msg = new RawMessage( '$1' );
+ $msg->params( [ Message::listParam( $list, $type ) ] );
+ $this->assertEquals(
+ $expect,
+ $msg->inLanguage( $lang )->$format()
+ );
+ }
+
/**
* @covers Message::extractParam
*/
$changeReq->password = $newpass;
$provider->providerChangeAuthenticationData( $changeReq );
- if ( $loginOnly ) {
+ if ( $loginOnly && $changed ) {
$old = 'fail';
$new = 'fail';
$expectExpiry = null;
--- /dev/null
+<?php
+
+namespace Wikimedia\Tests\Rdbms;
+
+use IDatabase;
+use LoadBalancer;
+use PHPUnit_Framework_MockObject_MockObject;
+use Wikimedia\Rdbms\ConnectionManager;
+
+/**
+ * @covers Wikimedia\Rdbms\ConnectionManager
+ *
+ * @license GPL-2.0+
+ * @author Daniel Kinzler
+ */
+class ConnectionManagerTest extends \PHPUnit_Framework_TestCase {
+
+ /**
+ * @return IDatabase|PHPUnit_Framework_MockObject_MockObject
+ */
+ private function getIDatabaseMock() {
+ return $this->getMock( IDatabase::class );
+ }
+
+ /**
+ * @return LoadBalancer|PHPUnit_Framework_MockObject_MockObject
+ */
+ private function getLoadBalancerMock() {
+ $lb = $this->getMockBuilder( LoadBalancer::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ return $lb;
+ }
+
+ public function testGetReadConnection_nullGroups() {
+ $database = $this->getIDatabaseMock();
+ $lb = $this->getLoadBalancerMock();
+
+ $lb->expects( $this->once() )
+ ->method( 'getConnection' )
+ ->with( DB_REPLICA, [ 'group1' ], 'someDbName' )
+ ->will( $this->returnValue( $database ) );
+
+ $manager = new ConnectionManager( $lb, 'someDbName', [ 'group1' ] );
+ $actual = $manager->getReadConnection();
+
+ $this->assertSame( $database, $actual );
+ }
+
+ public function testGetReadConnection_withGroups() {
+ $database = $this->getIDatabaseMock();
+ $lb = $this->getLoadBalancerMock();
+
+ $lb->expects( $this->once() )
+ ->method( 'getConnection' )
+ ->with( DB_REPLICA, [ 'group2' ], 'someDbName' )
+ ->will( $this->returnValue( $database ) );
+
+ $manager = new ConnectionManager( $lb, 'someDbName', [ 'group1' ] );
+ $actual = $manager->getReadConnection( [ 'group2' ] );
+
+ $this->assertSame( $database, $actual );
+ }
+
+ public function testGetWriteConnection() {
+ $database = $this->getIDatabaseMock();
+ $lb = $this->getLoadBalancerMock();
+
+ $lb->expects( $this->once() )
+ ->method( 'getConnection' )
+ ->with( DB_MASTER, [ 'group1' ], 'someDbName' )
+ ->will( $this->returnValue( $database ) );
+
+ $manager = new ConnectionManager( $lb, 'someDbName', [ 'group1' ] );
+ $actual = $manager->getWriteConnection();
+
+ $this->assertSame( $database, $actual );
+ }
+
+ public function testReleaseConnection() {
+ $database = $this->getIDatabaseMock();
+ $lb = $this->getLoadBalancerMock();
+
+ $lb->expects( $this->once() )
+ ->method( 'reuseConnection' )
+ ->with( $database )
+ ->will( $this->returnValue( null ) );
+
+ $manager = new ConnectionManager( $lb );
+ $manager->releaseConnection( $database );
+ }
+
+ public function testGetReadConnectionRef_nullGroups() {
+ $database = $this->getIDatabaseMock();
+ $lb = $this->getLoadBalancerMock();
+
+ $lb->expects( $this->once() )
+ ->method( 'getConnectionRef' )
+ ->with( DB_REPLICA, [ 'group1' ], 'someDbName' )
+ ->will( $this->returnValue( $database ) );
+
+ $manager = new ConnectionManager( $lb, 'someDbName', [ 'group1' ] );
+ $actual = $manager->getReadConnectionRef();
+
+ $this->assertSame( $database, $actual );
+ }
+
+ public function testGetReadConnectionRef_withGroups() {
+ $database = $this->getIDatabaseMock();
+ $lb = $this->getLoadBalancerMock();
+
+ $lb->expects( $this->once() )
+ ->method( 'getConnectionRef' )
+ ->with( DB_REPLICA, [ 'group2' ], 'someDbName' )
+ ->will( $this->returnValue( $database ) );
+
+ $manager = new ConnectionManager( $lb, 'someDbName', [ 'group1' ] );
+ $actual = $manager->getReadConnectionRef( [ 'group2' ] );
+
+ $this->assertSame( $database, $actual );
+ }
+
+ public function testGetWriteConnectionRef() {
+ $database = $this->getIDatabaseMock();
+ $lb = $this->getLoadBalancerMock();
+
+ $lb->expects( $this->once() )
+ ->method( 'getConnectionRef' )
+ ->with( DB_MASTER, [ 'group1' ], 'someDbName' )
+ ->will( $this->returnValue( $database ) );
+
+ $manager = new ConnectionManager( $lb, 'someDbName', [ 'group1' ] );
+ $actual = $manager->getWriteConnectionRef();
+
+ $this->assertSame( $database, $actual );
+ }
+
+}
--- /dev/null
+<?php
+
+namespace Wikimedia\Tests\Rdbms;
+
+use IDatabase;
+use LoadBalancer;
+use PHPUnit_Framework_MockObject_MockObject;
+use Wikimedia\Rdbms\SessionConsistentConnectionManager;
+
+/**
+ * @covers Wikimedia\Rdbms\SessionConsistentConnectionManager
+ *
+ * @license GPL-2.0+
+ * @author Daniel Kinzler
+ */
+class ConsistentReadConnectionManagerTest extends \PHPUnit_Framework_TestCase {
+
+ /**
+ * @return IDatabase|PHPUnit_Framework_MockObject_MockObject
+ */
+ private function getIDatabaseMock() {
+ return $this->getMock( IDatabase::class );
+ }
+
+ /**
+ * @return LoadBalancer|PHPUnit_Framework_MockObject_MockObject
+ */
+ private function getLoadBalancerMock() {
+ $lb = $this->getMockBuilder( LoadBalancer::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ return $lb;
+ }
+
+ public function testGetReadConnection() {
+ $database = $this->getIDatabaseMock();
+ $lb = $this->getLoadBalancerMock();
+
+ $lb->expects( $this->once() )
+ ->method( 'getConnection' )
+ ->with( DB_REPLICA )
+ ->will( $this->returnValue( $database ) );
+
+ $manager = new SessionConsistentConnectionManager( $lb );
+ $actual = $manager->getReadConnection();
+
+ $this->assertSame( $database, $actual );
+ }
+
+ public function testGetReadConnectionReturnsWriteDbOnForceMatser() {
+ $database = $this->getIDatabaseMock();
+ $lb = $this->getLoadBalancerMock();
+
+ $lb->expects( $this->once() )
+ ->method( 'getConnection' )
+ ->with( DB_MASTER )
+ ->will( $this->returnValue( $database ) );
+
+ $manager = new SessionConsistentConnectionManager( $lb );
+ $manager->prepareForUpdates();
+ $actual = $manager->getReadConnection();
+
+ $this->assertSame( $database, $actual );
+ }
+
+ public function testGetWriteConnection() {
+ $database = $this->getIDatabaseMock();
+ $lb = $this->getLoadBalancerMock();
+
+ $lb->expects( $this->once() )
+ ->method( 'getConnection' )
+ ->with( DB_MASTER )
+ ->will( $this->returnValue( $database ) );
+
+ $manager = new SessionConsistentConnectionManager( $lb );
+ $actual = $manager->getWriteConnection();
+
+ $this->assertSame( $database, $actual );
+ }
+
+ public function testForceMaster() {
+ $database = $this->getIDatabaseMock();
+ $lb = $this->getLoadBalancerMock();
+
+ $lb->expects( $this->once() )
+ ->method( 'getConnection' )
+ ->with( DB_MASTER )
+ ->will( $this->returnValue( $database ) );
+
+ $manager = new SessionConsistentConnectionManager( $lb );
+ $manager->prepareForUpdates();
+ $manager->getReadConnection();
+ }
+
+ public function testReleaseConnection() {
+ $database = $this->getIDatabaseMock();
+ $lb = $this->getLoadBalancerMock();
+
+ $lb->expects( $this->once() )
+ ->method( 'reuseConnection' )
+ ->with( $database )
+ ->will( $this->returnValue( null ) );
+
+ $manager = new SessionConsistentConnectionManager( $lb );
+ $manager->releaseConnection( $database );
+ }
+
+ public function testBeginAtomicSection() {
+ $database = $this->getIDatabaseMock();
+ $lb = $this->getLoadBalancerMock();
+
+ $lb->expects( $this->exactly( 2 ) )
+ ->method( 'getConnection' )
+ ->with( DB_MASTER )
+ ->will( $this->returnValue( $database ) );
+
+ $database->expects( $this->once() )
+ ->method( 'startAtomic' )
+ ->will( $this->returnValue( null ) );
+
+ $manager = new SessionConsistentConnectionManager( $lb );
+ $manager->beginAtomicSection( 'TEST' );
+
+ // Should also ask for a DB_MASTER connection.
+ // This is asserted by the $lb mock.
+ $manager->getReadConnection();
+ }
+
+ public function testCommitAtomicSection() {
+ $database = $this->getIDatabaseMock();
+ $lb = $this->getLoadBalancerMock();
+
+ $lb->expects( $this->once() )
+ ->method( 'reuseConnection' )
+ ->with( $database )
+ ->will( $this->returnValue( null ) );
+
+ $database->expects( $this->once() )
+ ->method( 'endAtomic' )
+ ->will( $this->returnValue( null ) );
+
+ $manager = new SessionConsistentConnectionManager( $lb );
+ $manager->commitAtomicSection( $database, 'TEST' );
+ }
+
+ public function testRollbackAtomicSection() {
+ $database = $this->getIDatabaseMock();
+ $lb = $this->getLoadBalancerMock();
+
+ $lb->expects( $this->once() )
+ ->method( 'reuseConnection' )
+ ->with( $database )
+ ->will( $this->returnValue( null ) );
+
+ $database->expects( $this->once() )
+ ->method( 'rollback' )
+ ->will( $this->returnValue( null ) );
+
+ $manager = new SessionConsistentConnectionManager( $lb );
+ $manager->rollbackAtomicSection( $database, 'TEST' );
+ }
+
+}