* The 'editcontentmodel' permission is now granted to all logged-in users ('user').
instead of just administrators ('sysop'). Documentation for this feature is
available at <https://www.mediawiki.org/wiki/Help:ChangeContentModel>.
+* $wgRevisionCacheExpiry is now set to one week by default instead of being disabled.
=== New features in 1.28 ===
* User::isBot() method for checking if an account is a bot role account.
* mw.Api has a new option, useUS, to use U+001F (Unit Separator) when
appropriate for sending multi-valued parameters. This defaults to true when
the mw.Api instance seems to be for the local wiki.
+* After a client performs an action which alters a database that has replica databases,
+ MediaWiki will wait for the replica databases to synchronize with the master database
+ while it renders the HTML output. However, if the output is a redirect to another wiki
+ on the wiki farm with a different domain, MediaWiki will instead alter the redirect
+ URL to include a ?cpPosTime parameter that triggers the database synchronization when
+ the URL is followed by the client. The same-domain case uses a new cpPosTime cookie.
=== External library changes in 1.28 ===
* OOjs UI PHP widgets constructed with the `'infusable' => true` config option
will no longer be automatically infused. You should call `OO.ui.infuse()`
on them yourself from your JavaScript code.
+* parserTests.php has moved to tests/parser/parserTests.php
+* The command line options specific to parser tests have been removed from
+ phpunit.php: --regex and --keep-uploads. Instead of --regex, use --filter.
+ Instead of --keep-uploads, use the same option to parserTests.php, but you
+ must specify a directory with --upload-dir.
== Compatibility ==
'BitmapHandler' => __DIR__ . '/includes/media/Bitmap.php',
'BitmapHandler_ClientOnly' => __DIR__ . '/includes/media/Bitmap_ClientOnly.php',
'BitmapMetadataHandler' => __DIR__ . '/includes/media/BitmapMetadataHandler.php',
- 'Blob' => __DIR__ . '/includes/db/DatabaseUtility.php',
+ 'Blob' => __DIR__ . '/includes/libs/rdbms/encasing/Blob.php',
'Block' => __DIR__ . '/includes/Block.php',
'BlockLevelPass' => __DIR__ . '/includes/parser/BlockLevelPass.php',
'BlockListPager' => __DIR__ . '/includes/specials/pagers/BlockListPager.php',
'CheckStorage' => __DIR__ . '/maintenance/storage/checkStorage.php',
'CheckSyntax' => __DIR__ . '/maintenance/checkSyntax.php',
'CheckUsernames' => __DIR__ . '/maintenance/checkUsernames.php',
- 'ChronologyProtector' => __DIR__ . '/includes/db/ChronologyProtector.php',
+ 'ChronologyProtector' => __DIR__ . '/includes/libs/rdbms/chronologyprotector/ChronologyProtector.php',
'ClassCollector' => __DIR__ . '/includes/utils/AutoloadGenerator.php',
'CleanupAncientTables' => __DIR__ . '/maintenance/cleanupAncientTables.php',
'CleanupBlocks' => __DIR__ . '/maintenance/cleanupBlocks.php',
'CsvStatsOutput' => __DIR__ . '/maintenance/language/StatOutputs.php',
'CurlHttpRequest' => __DIR__ . '/includes/HttpFunctions.php',
'DBAccessBase' => __DIR__ . '/includes/dao/DBAccessBase.php',
- 'DBAccessError' => __DIR__ . '/includes/db/DatabaseError.php',
+ 'DBAccessError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
'DBAccessObjectUtils' => __DIR__ . '/includes/dao/DBAccessObjectUtils.php',
- 'DBConnRef' => __DIR__ . '/includes/db/DBConnRef.php',
- 'DBConnectionError' => __DIR__ . '/includes/db/DatabaseError.php',
- 'DBError' => __DIR__ . '/includes/db/DatabaseError.php',
- 'DBExpectedError' => __DIR__ . '/includes/db/DatabaseError.php',
+ 'DBConnRef' => __DIR__ . '/includes/libs/rdbms/database/DBConnRef.php',
+ 'DBConnectionError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
+ 'DBError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
+ 'DBExpectedError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
'DBFileJournal' => __DIR__ . '/includes/filebackend/filejournal/DBFileJournal.php',
'DBLockManager' => __DIR__ . '/includes/filebackend/lockmanager/DBLockManager.php',
- 'DBMasterPos' => __DIR__ . '/includes/db/DatabaseUtility.php',
- 'DBQueryError' => __DIR__ . '/includes/db/DatabaseError.php',
- 'DBReadOnlyError' => __DIR__ . '/includes/db/DatabaseError.php',
- 'DBReplicationWaitError' => __DIR__ . '/includes/db/DatabaseError.php',
+ 'DBMasterPos' => __DIR__ . '/includes/libs/rdbms/database/position/DBMasterPos.php',
+ 'DBQueryError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
+ 'DBReadOnlyError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
+ 'DBReplicationWaitError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
'DBSiteStore' => __DIR__ . '/includes/site/DBSiteStore.php',
- 'DBTransactionError' => __DIR__ . '/includes/db/DatabaseError.php',
- 'DBUnexpectedError' => __DIR__ . '/includes/db/DatabaseError.php',
+ 'DBTransactionError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
+ 'DBTransactionSizeError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
+ 'DBUnexpectedError' => __DIR__ . '/includes/libs/rdbms/exception/DBError.php',
'DataUpdate' => __DIR__ . '/includes/deferred/DataUpdate.php',
'Database' => __DIR__ . '/includes/db/Database.php',
'DatabaseBase' => __DIR__ . '/includes/db/Database.php',
'FakeAuthTemplate' => __DIR__ . '/includes/specialpage/LoginSignupSpecialPage.php',
'FakeConverter' => __DIR__ . '/languages/FakeConverter.php',
'FakeMaintenance' => __DIR__ . '/maintenance/Maintenance.php',
- 'FakeResultWrapper' => __DIR__ . '/includes/db/DatabaseUtility.php',
+ 'FakeResultWrapper' => __DIR__ . '/includes/libs/rdbms/database/resultwrapper/FakeResultWrapper.php',
'FatalError' => __DIR__ . '/includes/exception/FatalError.php',
'FauxRequest' => __DIR__ . '/includes/FauxRequest.php',
'FauxResponse' => __DIR__ . '/includes/WebResponse.php',
'FeedUtils' => __DIR__ . '/includes/FeedUtils.php',
'FetchText' => __DIR__ . '/maintenance/fetchText.php',
'FewestrevisionsPage' => __DIR__ . '/includes/specials/SpecialFewestrevisions.php',
- 'Field' => __DIR__ . '/includes/db/DatabaseUtility.php',
+ 'Field' => __DIR__ . '/includes/libs/rdbms/field/Field.php',
'File' => __DIR__ . '/includes/filerepo/file/File.php',
'FileAwareNodeVisitor' => __DIR__ . '/maintenance/findDeprecated.php',
'FileBackend' => __DIR__ . '/includes/filebackend/FileBackend.php',
'ICacheHelper' => __DIR__ . '/includes/cache/CacheHelper.php',
'IContextSource' => __DIR__ . '/includes/context/IContextSource.php',
'IDBAccessObject' => __DIR__ . '/includes/dao/IDBAccessObject.php',
- 'IDatabase' => __DIR__ . '/includes/db/IDatabase.php',
+ 'IDatabase' => __DIR__ . '/includes/libs/rdbms/database/IDatabase.php',
'IEContentAnalyzer' => __DIR__ . '/includes/libs/IEContentAnalyzer.php',
'IEUrlExtension' => __DIR__ . '/includes/libs/IEUrlExtension.php',
'IExpiringStore' => __DIR__ . '/includes/libs/objectcache/IExpiringStore.php',
'IJobSpecification' => __DIR__ . '/includes/jobqueue/JobSpecification.php',
+ 'ILoadBalancer' => __DIR__ . '/includes/libs/rdbms/loadbalancer/ILoadBalancer.php',
'IP' => __DIR__ . '/includes/utils/IP.php',
'IPSet' => __DIR__ . '/includes/compat/IPSetCompat.php',
'IPTC' => __DIR__ . '/includes/media/IPTC.php',
'KkConverter' => __DIR__ . '/languages/classes/LanguageKk.php',
'KuConverter' => __DIR__ . '/languages/classes/LanguageKu.php',
'LBFactory' => __DIR__ . '/includes/db/loadbalancer/LBFactory.php',
- 'LBFactoryFake' => __DIR__ . '/includes/db/loadbalancer/LBFactoryFake.php',
'LBFactoryMulti' => __DIR__ . '/includes/db/loadbalancer/LBFactoryMulti.php',
'LBFactorySimple' => __DIR__ . '/includes/db/loadbalancer/LBFactorySimple.php',
'LBFactorySingle' => __DIR__ . '/includes/db/loadbalancer/LBFactorySingle.php',
'LegacyLogFormatter' => __DIR__ . '/includes/logging/LogFormatter.php',
'License' => __DIR__ . '/includes/Licenses.php',
'Licenses' => __DIR__ . '/includes/Licenses.php',
- 'LikeMatch' => __DIR__ . '/includes/db/DatabaseUtility.php',
+ 'LikeMatch' => __DIR__ . '/includes/libs/rdbms/encasing/LikeMatch.php',
'LinkBatch' => __DIR__ . '/includes/cache/LinkBatch.php',
'LinkCache' => __DIR__ . '/includes/cache/LinkCache.php',
'LinkFilter' => __DIR__ . '/includes/LinkFilter.php',
'ListToggle' => __DIR__ . '/includes/ListToggle.php',
'ListVariants' => __DIR__ . '/maintenance/language/listVariants.php',
'ListredirectsPage' => __DIR__ . '/includes/specials/SpecialListredirects.php',
- 'LoadBalancer' => __DIR__ . '/includes/db/loadbalancer/LoadBalancer.php',
+ 'LoadBalancer' => __DIR__ . '/includes/libs/rdbms/loadbalancer/LoadBalancer.php',
'LoadBalancerSingle' => __DIR__ . '/includes/db/loadbalancer/LBFactorySingle.php',
- 'LoadMonitor' => __DIR__ . '/includes/db/loadbalancer/LoadMonitor.php',
- 'LoadMonitorMySQL' => __DIR__ . '/includes/db/loadbalancer/LoadMonitorMySQL.php',
- 'LoadMonitorNull' => __DIR__ . '/includes/db/loadbalancer/LoadMonitor.php',
+ 'LoadMonitor' => __DIR__ . '/includes/libs/rdbms/loadmonitor/LoadMonitor.php',
+ 'LoadMonitorMySQL' => __DIR__ . '/includes/libs/rdbms/loadmonitor/LoadMonitorMySQL.php',
+ 'LoadMonitorNull' => __DIR__ . '/includes/libs/rdbms/loadmonitor/LoadMonitorNull.php',
'LocalFile' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
'LocalFileDeleteBatch' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
'LocalFileLockError' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
'MWDocGen' => __DIR__ . '/maintenance/mwdocgen.php',
'MWException' => __DIR__ . '/includes/exception/MWException.php',
'MWExceptionHandler' => __DIR__ . '/includes/exception/MWExceptionHandler.php',
+ 'MWExceptionRenderer' => __DIR__ . '/includes/exception/MWExceptionRenderer.php',
'MWGrants' => __DIR__ . '/includes/utils/MWGrants.php',
'MWHttpRequest' => __DIR__ . '/includes/HttpFunctions.php',
'MWMemcached' => __DIR__ . '/includes/compat/MemcachedClientCompat.php',
'MemcLockManager' => __DIR__ . '/includes/filebackend/lockmanager/MemcLockManager.php',
'MemcachedBagOStuff' => __DIR__ . '/includes/libs/objectcache/MemcachedBagOStuff.php',
'MemcachedClient' => __DIR__ . '/includes/libs/objectcache/MemcachedClient.php',
- 'MemcachedPeclBagOStuff' => __DIR__ . '/includes/objectcache/MemcachedPeclBagOStuff.php',
+ 'MemcachedPeclBagOStuff' => __DIR__ . '/includes/libs/objectcache/MemcachedPeclBagOStuff.php',
'MemcachedPhpBagOStuff' => __DIR__ . '/includes/libs/objectcache/MemcachedPhpBagOStuff.php',
'MemoizedCallable' => __DIR__ . '/includes/libs/MemoizedCallable.php',
'MemoryFileBackend' => __DIR__ . '/includes/filebackend/MemoryFileBackend.php',
'MoveLogFormatter' => __DIR__ . '/includes/logging/MoveLogFormatter.php',
'MovePage' => __DIR__ . '/includes/MovePage.php',
'MovePageForm' => __DIR__ . '/includes/specials/SpecialMovepage.php',
- 'MssqlBlob' => __DIR__ . '/includes/db/DatabaseMssql.php',
- 'MssqlField' => __DIR__ . '/includes/db/DatabaseMssql.php',
+ 'MssqlBlob' => __DIR__ . '/includes/libs/rdbms/encasing/MssqlBlob.php',
+ 'MssqlField' => __DIR__ . '/includes/libs/rdbms/field/MssqlField.php',
'MssqlInstaller' => __DIR__ . '/includes/installer/MssqlInstaller.php',
- 'MssqlResultWrapper' => __DIR__ . '/includes/db/DatabaseMssql.php',
+ 'MssqlResultWrapper' => __DIR__ . '/includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php',
'MssqlUpdater' => __DIR__ . '/includes/installer/MssqlUpdater.php',
'MultiConfig' => __DIR__ . '/includes/config/MultiConfig.php',
'MultiHttpClient' => __DIR__ . '/includes/libs/MultiHttpClient.php',
'MutableConfig' => __DIR__ . '/includes/config/MutableConfig.php',
'MutableContext' => __DIR__ . '/includes/context/MutableContext.php',
'MwSql' => __DIR__ . '/maintenance/sql.php',
- 'MySQLField' => __DIR__ . '/includes/db/DatabaseMysqlBase.php',
- 'MySQLMasterPos' => __DIR__ . '/includes/db/DatabaseMysqlBase.php',
+ 'MySQLField' => __DIR__ . '/includes/libs/rdbms/field/MySQLField.php',
+ 'MySQLMasterPos' => __DIR__ . '/includes/libs/rdbms/database/position/MySQLMasterPos.php',
'MySqlLockManager' => __DIR__ . '/includes/filebackend/lockmanager/MySqlLockManager.php',
'MysqlInstaller' => __DIR__ . '/includes/installer/MysqlInstaller.php',
'MysqlUpdater' => __DIR__ . '/includes/installer/MysqlUpdater.php',
'NullStatsdDataFactory' => __DIR__ . '/includes/libs/stats/NullStatsdDataFactory.php',
'NumericUppercaseCollation' => __DIR__ . '/includes/collation/NumericUppercaseCollation.php',
'OOUIHTMLForm' => __DIR__ . '/includes/htmlform/OOUIHTMLForm.php',
- 'ORAField' => __DIR__ . '/includes/db/DatabaseOracle.php',
+ 'ORAField' => __DIR__ . '/includes/libs/rdbms/field/ORAField.php',
'ORAResult' => __DIR__ . '/includes/db/DatabaseOracle.php',
'ObjectCache' => __DIR__ . '/includes/objectcache/ObjectCache.php',
'ObjectFactory' => __DIR__ . '/includes/libs/ObjectFactory.php',
'PopulateRevisionLength' => __DIR__ . '/maintenance/populateRevisionLength.php',
'PopulateRevisionSha1' => __DIR__ . '/maintenance/populateRevisionSha1.php',
'PostgreSqlLockManager' => __DIR__ . '/includes/filebackend/lockmanager/PostgreSqlLockManager.php',
- 'PostgresBlob' => __DIR__ . '/includes/db/DatabasePostgres.php',
+ 'PostgresBlob' => __DIR__ . '/includes/libs/rdbms/encasing/PostgresBlob.php',
'PostgresField' => __DIR__ . '/includes/db/DatabasePostgres.php',
'PostgresInstaller' => __DIR__ . '/includes/installer/PostgresInstaller.php',
'PostgresUpdater' => __DIR__ . '/includes/installer/PostgresUpdater.php',
'ResourceLoaderUserTokensModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserTokensModule.php',
'ResourceLoaderWikiModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderWikiModule.php',
'RestbaseVirtualRESTService' => __DIR__ . '/includes/libs/virtualrest/RestbaseVirtualRESTService.php',
- 'ResultWrapper' => __DIR__ . '/includes/db/DatabaseUtility.php',
+ 'ResultWrapper' => __DIR__ . '/includes/libs/rdbms/database/resultwrapper/ResultWrapper.php',
'RevDelArchiveItem' => __DIR__ . '/includes/revisiondelete/RevDelArchiveItem.php',
'RevDelArchiveList' => __DIR__ . '/includes/revisiondelete/RevDelArchiveList.php',
'RevDelArchivedFileItem' => __DIR__ . '/includes/revisiondelete/RevDelArchivedFileItem.php',
'RowUpdateGenerator' => __DIR__ . '/includes/utils/RowUpdateGenerator.php',
'RunJobs' => __DIR__ . '/maintenance/runJobs.php',
'RunningStat' => __DIR__ . '/includes/compat/RunningStatCompat.php',
- 'SQLiteField' => __DIR__ . '/includes/db/DatabaseSqlite.php',
+ 'SQLiteField' => __DIR__ . '/includes/libs/rdbms/field/SQLiteField.php',
'SVGMetadataExtractor' => __DIR__ . '/includes/media/SVGMetadataExtractor.php',
'SVGReader' => __DIR__ . '/includes/media/SVGMetadataExtractor.php',
'SamplingStatsdClient' => __DIR__ . '/includes/libs/SamplingStatsdClient.php',
'TitleValue' => __DIR__ . '/includes/title/TitleValue.php',
'TrackBlobs' => __DIR__ . '/maintenance/storage/trackBlobs.php',
'TraditionalImageGallery' => __DIR__ . '/includes/gallery/TraditionalImageGallery.php',
- 'TransactionProfiler' => __DIR__ . '/includes/profiler/TransactionProfiler.php',
+ 'TransactionProfiler' => __DIR__ . '/includes/libs/rdbms/TransactionProfiler.php',
'TransformParameterError' => __DIR__ . '/includes/media/MediaTransformOutput.php',
'TransformTooBigImageAreaError' => __DIR__ . '/includes/media/MediaTransformOutput.php',
'TransformationalImageHandler' => __DIR__ . '/includes/media/TransformationalImageHandler.php',
"ext-xml": "*",
"liuggio/statsd-php-client": "1.0.18",
"mediawiki/at-ease": "1.1.0",
- "oojs/oojs-ui": "0.17.8",
+ "oojs/oojs-ui": "0.17.9",
"oyejorge/less.php": "1.7.0.10",
"php": ">=5.5.9",
"psr/log": "1.0.0",
*
* Set to 0 to disable, or number of seconds before cache expiry.
*/
-$wgRevisionCacheExpiry = 0;
+$wgRevisionCacheExpiry = 86400 * 7;
/** @} */ # end text storage }
*/
$wgTranscludeCacheExpiry = 3600;
+/**
+ * Enable the magic links feature of automatically turning ISBN xxx,
+ * PMID xxx, RFC xxx into links
+ *
+ * @since 1.28
+ */
+$wgEnableMagicLinks = [
+ 'ISBN' => true,
+ 'PMID' => true,
+ 'RFC' => true
+];
+
/** @} */ # end of parser settings }
/************************************************************************//**
'POST' => [
'readQueryTime' => 5,
'writeQueryTime' => 1,
- 'maxAffected' => 500
+ 'maxAffected' => 1000
],
'POST-nonwrite' => [
'masterConns' => 0,
'PostSend' => [
'readQueryTime' => 5,
'writeQueryTime' => 1,
- 'maxAffected' => 500
+ 'maxAffected' => 1000
],
// Background job runner
'JobRunner' => [
// May be overridden by revision.
$this->contentFormat = $request->getText( 'format', $this->contentFormat );
- if ( !ContentHandler::getForModelID( $this->contentModel )
- ->isSupportedFormat( $this->contentFormat )
- ) {
+ try {
+ $handler = ContentHandler::getForModelID( $this->contentModel );
+ } catch ( MWUnknownContentModelException $e ) {
+ throw new ErrorPageError(
+ 'editpage-invalidcontentmodel-title',
+ 'editpage-invalidcontentmodel-text',
+ [ $this->contentModel ]
+ );
+ }
+
+ if ( !$handler->isSupportedFormat( $this->contentFormat ) ) {
throw new ErrorPageError(
'editpage-notsupportedcontentformat-title',
'editpage-notsupportedcontentformat-text',
* subclasses may reorganize the form.
* Note that you do not need to worry about the label's for=, it will be
* inferred by the id given to the input. You can remove them both by
- * passing array( 'id' => false ) to $userInputAttrs.
+ * passing [ 'id' => false ] to $userInputAttrs.
*
* @param string $summary The value of the summary input
* @param string $labelText The html to place inside the label
* @param array $inputAttrs Array of attrs to use on the input
* @param array $spanLabelAttrs Array of attrs to use on the span inside the label
*
- * @return array An array in the format array( $label, $input )
+ * @return array An array in the format [ $label, $input ]
*/
function getSummaryInput( $summary = "", $labelText = null,
$inputAttrs = null, $spanLabelAttrs = null
*
* ;:@$!*(),/~
*
- * However, IIS7 redirects fail when the url contains a colon (Bug 22709),
+ * However, IIS7 redirects fail when the url contains a colon (see T24709),
* so no fancy : for IIS7.
*
* %2F in the page titles seems to fatally break for some reason.
* This is the basic structure used (brackets contain keys for $urlParts):
* [scheme][delimiter][user]:[pass]@[host]:[port][path]?[query]#[fragment]
*
- * @todo Need to integrate this into wfExpandUrl (bug 32168)
+ * @todo Need to integrate this into wfExpandUrl (see T34168)
*
* @since 1.19
* @param array $urlParts URL parts, as output from wfParseUrl
* '/a/./b/../c/' becomes '/a/c/'. For details on the algorithm, please see
* RFC3986 section 5.2.4.
*
- * @todo Need to integrate this into wfExpandUrl (bug 32168)
+ * @todo Need to integrate this into wfExpandUrl (see T34168)
*
* @param string $urlPath URL path, potentially containing dot-segments
* @return string URL path with all dot-segments removed
return false;
}
- /* Provide an empty host for eg. file:/// urls (see bug 28627) */
+ /* Provide an empty host for eg. file:/// urls (see T30627) */
if ( !isset( $bits['host'] ) ) {
$bits['host'] = '';
- // bug 45069
+ // See T47069
if ( isset( $bits['path'] ) ) {
/* parse_url loses the third / for file:///c:/ urls (but not on variants) */
if ( substr( $bits['path'], 0, 1 ) !== '/' ) {
* @return string
*/
function wfEscapeWikiText( $text ) {
+ global $wgEnableMagicLinks;
static $repl = null, $repl2 = null;
if ( $repl === null ) {
$repl = [
'__' => '__', '://' => '://',
];
+ $magicLinks = array_keys( array_filter( $wgEnableMagicLinks ) );
// We have to catch everything "\s" matches in PCRE
- foreach ( [ 'ISBN', 'RFC', 'PMID' ] as $magic ) {
+ foreach ( $magicLinks as $magic ) {
$repl["$magic "] = "$magic ";
$repl["$magic\t"] = "$magic	";
$repl["$magic\r"] = "$magic ";
// Refs:
// * http://web.archive.org/web/20020708081031/http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html
// * http://technet.microsoft.com/en-us/library/cc723564.aspx
- // * Bug #13518
+ // * T15518
// * CR r63214
// Double the backslashes before any double quotes. Escape the double quotes.
// @codingStandardsIgnoreEnd
* @throws MWException If not in testing mode.
*/
public static function clear( $name ) {
- if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+ if ( !defined( 'MW_PHPUNIT_TEST' ) && !defined( 'MW_PARSER_TEST' ) ) {
throw new MWException( 'Cannot reset hooks in operation.' );
}
*
* @par Example:
* @code
- * $magicWords = array();
+ * $magicWords = [];
*
- * $magicWords['en'] = array(
- * 'magicwordkey' => array( 0, 'case_insensitive_magic_word' ),
- * 'magicwordkey2' => array( 1, 'CASE_sensitive_magic_word2' ),
- * );
+ * $magicWords['en'] = [
+ * 'magicwordkey' => [ 0, 'case_insensitive_magic_word' ],
+ * 'magicwordkey2' => [ 1, 'CASE_sensitive_magic_word2' ],
+ * ];
* @endcode
*
* For magic words which are also Parser variables, add a MagicWordwgVariableIDs
/**
* @see MediaWiki::preOutputCommit()
+ * @param callable $postCommitWork [default: null]
* @since 1.26
*/
- public function doPreOutputCommit() {
- self::preOutputCommit( $this->context );
+ public function doPreOutputCommit( callable $postCommitWork = null ) {
+ self::preOutputCommit( $this->context, $postCommitWork );
}
/**
* the user can receive a response (in case commit fails)
*
* @param IContextSource $context
+ * @param callable $postCommitWork [default: null]
* @since 1.27
*/
- public static function preOutputCommit( IContextSource $context ) {
+ public static function preOutputCommit(
+ IContextSource $context, callable $postCommitWork = null
+ ) {
// Either all DBs should commit or none
ignore_user_abort( true );
$config = $context->getConfig();
-
+ $request = $context->getRequest();
+ $output = $context->getOutput();
$lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+
// Commit all changes
$lbFactory->commitMasterChanges(
__METHOD__,
// Abort if any transaction was too big
[ 'maxWriteDuration' => $config->get( 'MaxUserDBWriteDuration' ) ]
);
+ wfDebug( __METHOD__ . ': primary transaction round committed' );
+ // Run updates that need to block the user or affect output (this is the last chance)
DeferredUpdates::doUpdates( 'enqueue', DeferredUpdates::PRESEND );
wfDebug( __METHOD__ . ': pre-send deferred updates completed' );
- // Record ChronologyProtector positions
- $lbFactory->shutdown();
- wfDebug( __METHOD__ . ': all transactions committed' );
+ // Decide when clients block on ChronologyProtector DB position writes
+ $urlDomainDistance = (
+ $request->wasPosted() &&
+ $output->getRedirect() &&
+ $lbFactory->hasOrMadeRecentMasterChanges( INF )
+ ) ? self::getUrlDomainDistance( $output->getRedirect(), $context ) : false;
+
+ if ( $urlDomainDistance === 'local' || $urlDomainDistance === 'remote' ) {
+ // OutputPage::output() will be fast; $postCommitWork will not be useful for
+ // masking the latency of syncing DB positions accross all datacenters synchronously.
+ // Instead, make use of the RTT time of the client follow redirects.
+ $flags = $lbFactory::SHUTDOWN_CHRONPROT_ASYNC;
+ $cpPosTime = microtime( true );
+ // Client's next request should see 1+ positions with this DBMasterPos::asOf() time
+ if ( $urlDomainDistance === 'local' ) {
+ // Client will stay on this domain, so set an unobtrusive cookie
+ $expires = time() + ChronologyProtector::POSITION_TTL;
+ $options = [ 'prefix' => '' ];
+ $request->response()->setCookie( 'cpPosTime', $cpPosTime, $expires, $options );
+ } else {
+ // Cookies may not work across wiki domains, so use a URL parameter
+ $safeUrl = $lbFactory->appendPreShutdownTimeAsQuery(
+ $output->getRedirect(),
+ $cpPosTime
+ );
+ $output->redirect( $safeUrl );
+ }
+ } else {
+ // OutputPage::output() is fairly slow; run it in $postCommitWork to mask
+ // the latency of syncing DB positions accross all datacenters synchronously
+ $flags = $lbFactory::SHUTDOWN_CHRONPROT_SYNC;
+ if ( $lbFactory->hasOrMadeRecentMasterChanges( INF ) ) {
+ $cpPosTime = microtime( true );
+ // Set a cookie in case the DB position store cannot sync accross datacenters.
+ // This will at least cover the common case of the user staying on the domain.
+ $expires = time() + ChronologyProtector::POSITION_TTL;
+ $options = [ 'prefix' => '' ];
+ $request->response()->setCookie( 'cpPosTime', $cpPosTime, $expires, $options );
+ }
+ }
+ // Record ChronologyProtector positions for DBs affected in this request at this point
+ $lbFactory->shutdown( $flags, $postCommitWork );
+ wfDebug( __METHOD__ . ': LBFactory shutdown completed' );
// Set a cookie to tell all CDN edge nodes to "stick" the user to the DC that handles this
// POST request (e.g. the "master" data center). Also have the user briefly bypass CDN so
// ChronologyProtector works for cacheable URLs.
- $request = $context->getRequest();
if ( $request->wasPosted() && $lbFactory->hasOrMadeRecentMasterChanges() ) {
$expires = time() + $config->get( 'DataCenterUpdateStickTTL' );
$options = [ 'prefix' => '' ];
// also intimately related to the value of $wgCdnReboundPurgeDelay.
if ( $lbFactory->laggedReplicaUsed() ) {
$maxAge = $config->get( 'CdnMaxageLagged' );
- $context->getOutput()->lowerCdnMaxage( $maxAge );
+ $output->lowerCdnMaxage( $maxAge );
$request->response()->header( "X-Database-Lagged: true" );
wfDebugLog( 'replication', "Lagged DB used; CDN cache TTL limited to $maxAge seconds" );
}
// Avoid long-term cache pollution due to message cache rebuild timeouts (T133069)
if ( MessageCache::singleton()->isDisabled() ) {
$maxAge = $config->get( 'CdnMaxageSubstitute' );
- $context->getOutput()->lowerCdnMaxage( $maxAge );
+ $output->lowerCdnMaxage( $maxAge );
$request->response()->header( "X-Response-Substitute: true" );
}
}
+ /**
+ * @param string $url
+ * @param IContextSource $context
+ * @return string|bool Either "local" or "remote" if in the farm, false otherwise
+ */
+ private function getUrlDomainDistance( $url, IContextSource $context ) {
+ static $relevantKeys = [ 'host' => true, 'port' => true ];
+
+ $infoCandidate = wfParseUrl( $url );
+ if ( $infoCandidate === false ) {
+ return false;
+ }
+
+ $infoCandidate = array_intersect_key( $infoCandidate, $relevantKeys );
+ $clusterHosts = array_merge(
+ // Local wiki host (the most common case)
+ [ $context->getConfig()->get( 'CanonicalServer' ) ],
+ // Any local/remote wiki virtual hosts for this wiki farm
+ $context->getConfig()->get( 'LocalVirtualHosts' )
+ );
+
+ foreach ( $clusterHosts as $i => $clusterHost ) {
+ $parseUrl = wfParseUrl( $clusterHost );
+ if ( !$parseUrl ) {
+ continue;
+ }
+ $infoHost = array_intersect_key( $parseUrl, $relevantKeys );
+ if ( $infoCandidate === $infoHost ) {
+ return ( $i === 0 ) ? 'local' : 'remote';
+ }
+ }
+
+ return false;
+ }
+
/**
* This function does work that can be done *after* the
* user gets the HTTP response so they don't block on it
// Show visible profiling data if enabled (which cannot be post-send)
Profiler::instance()->logDataPageOutputOnly();
- $that = $this;
- $callback = function () use ( $that, $mode ) {
+ $callback = function () use ( $mode ) {
try {
- $that->restInPeace( $mode );
+ $this->restInPeace( $mode );
} catch ( Exception $e ) {
MWExceptionHandler::handleException( $e );
}
private function main() {
global $wgTitle;
+ $output = $this->context->getOutput();
$request = $this->context->getRequest();
// Send Ajax requests to the Ajax dispatcher.
$dispatcher = new AjaxDispatcher( $this->config );
$dispatcher->performAction( $this->context->getUser() );
+
return;
}
// Setup dummy Title, otherwise OutputPage::redirect will fail
$title = Title::newFromText( 'REDIR', NS_MAIN );
$this->context->setTitle( $title );
- $output = $this->context->getOutput();
// Since we only do this redir to change proto, always send a vary header
$output->addVaryHeader( 'X-Forwarded-Proto' );
$output->redirect( $redirUrl );
$output->output();
+
return;
}
}
if ( $cache->isCacheGood( /* Assume up to date */ ) ) {
// Check incoming headers to see if client has this cached
$timestamp = $cache->cacheTimestamp();
- if ( !$this->context->getOutput()->checkLastModified( $timestamp ) ) {
+ if ( !$output->checkLastModified( $timestamp ) ) {
$cache->loadFromFileCache( $this->context );
}
// Do any stats increment/watchlist stuff
// Assume we're viewing the latest revision (this should always be the case with file cache)
$this->context->getWikiPage()->doViewUpdates( $this->context->getUser() );
// Tell OutputPage that output is taken care of
- $this->context->getOutput()->disable();
+ $output->disable();
+
return;
}
}
// Actually do the work of the request and build up any output
$this->performRequest();
+ // GUI-ify and stash the page output in MediaWiki::doPreOutputCommit() while
+ // ChronologyProtector synchronizes DB positions or slaves accross all datacenters.
+ $buffer = null;
+ $outputWork = function () use ( $output, &$buffer ) {
+ if ( $buffer === null ) {
+ $buffer = $output->output( true );
+ }
+
+ return $buffer;
+ };
+
// Now commit any transactions, so that unreported errors after
// output() don't roll back the whole DB transaction and so that
// we avoid having both success and error text in the response
- $this->doPreOutputCommit();
+ $this->doPreOutputCommit( $outputWork );
- // Output everything!
- $this->context->getOutput()->output();
+ // Now send the actual output
+ print $outputWork();
}
/**
/**
* Finally, all the text has been munged and accumulated into
* the object, let's actually output it:
+ *
+ * @param bool $return Set to true to get the result as a string rather than sending it
+ * @return string|null
+ * @throws Exception
+ * @throws FatalError
+ * @throws MWException
*/
- public function output() {
+ public function output( $return = false ) {
if ( $this->mDoNothing ) {
- return;
+ return $return ? '' : null;
}
$response = $this->getRequest()->response();
}
}
- return;
+ return $return ? '' : null;
} elseif ( $this->mStatusCode ) {
$response->statusHeader( $this->mStatusCode );
}
$this->sendCacheControl();
- ob_end_flush();
-
+ if ( $return ) {
+ return ob_get_clean();
+ } else {
+ ob_end_flush();
+ return null;
+ }
}
/**
);
$this->rlExemptStyleModules = $exemptGroups;
- // Manually handled by getBottomScripts()
- $userModule = $rl->getModule( 'user' );
- $userState = $userModule->isKnownEmpty( $context ) && !$this->isUserJsPreview()
- ? 'ready'
- : 'loading';
- $this->rlUserModuleState = $exemptStates['user'] = $userState;
+ $isUserModuleFiltered = !$this->filterModules( [ 'user' ] );
+ // If this page filters out 'user', makeResourceLoaderLink will drop it.
+ // Avoid indefinite "loading" state or untrue "ready" state (T145368).
+ if ( !$isUserModuleFiltered ) {
+ // Manually handled by getBottomScripts()
+ $userModule = $rl->getModule( 'user' );
+ $userState = $userModule->isKnownEmpty( $context ) && !$this->isUserJsPreview()
+ ? 'ready'
+ : 'loading';
+ $this->rlUserModuleState = $exemptStates['user'] = $userState;
+ }
$rlClient = new ResourceLoaderClientHtml( $context, $this->getTarget() );
$rlClient->setConfig( $this->getJSVars() );
*
* $router->add( "/wiki/$1" );
* - Matches /wiki/Foo style urls and extracts the title
- * $router->add( array( 'edit' => "/edit/$key" ), array( 'action' => '$key' ) );
+ * $router->add( [ 'edit' => "/edit/$key" ], [ 'action' => '$key' ] );
* - Matches /edit/Foo style urls and sets action=edit
* $router->add( '/$2/$1',
- * array( 'variant' => '$2' ),
- * array( '$2' => array( 'zh-hant', 'zh-hans' )
+ * [ 'variant' => '$2' ],
+ * [ '$2' => [ 'zh-hant', 'zh-hans' ] ]
* );
* - Matches /zh-hant/Foo or /zh-hans/Foo
- * $router->addStrict( "/foo/Bar", array( 'title' => 'Baz' ) );
+ * $router->addStrict( "/foo/Bar", [ 'title' => 'Baz' ] );
* - Matches /foo/Bar explicitly and uses "Baz" as the title
- * $router->add( '/help/$1', array( 'title' => 'Help:$1' ) );
+ * $router->add( '/help/$1', [ 'title' => 'Help:$1' ] );
* - Matches /help/Foo with "Help:Foo" as the title
- * $router->add( '/$1', array( 'foo' => array( 'value' => 'bar$2' ) );
+ * $router->add( '/$1', [ 'foo' => [ 'value' => 'bar$2' ] ] );
* - Matches /Foo and sets 'foo' to 'bar$2' without $2 being replaced
- * $router->add( '/$1', array( 'data:foo' => 'bar' ), array( 'callback' => 'functionname' ) );
+ * $router->add( '/$1', [ 'data:foo' => 'bar' ], [ 'callback' => 'functionname' ] );
* - Matches /Foo, adds the key 'foo' with the value 'bar' to the data array
* and calls functionname( &$matches, $data );
*
* - The default behavior is equivalent to `array( 'title' => '$1' )`,
* if you don't want the title parameter you can explicitly use `array( 'title' => false )`
* - You can specify a value that won't have replacements in it
- * using `'foo' => array( 'value' => 'bar' );`
+ * using `'foo' => [ 'value' => 'bar' ];`
*
* Options:
* - The option keys $1, $2, etc... can be specified to restrict the possible values
* @return string|bool The revision's text, or false on failure
*/
private function loadText() {
- // Caching may be beneficial for massive use of external storage
global $wgRevisionCacheExpiry;
- if ( !$wgRevisionCacheExpiry ) {
- return $this->fetchText();
- }
-
$cache = ObjectCache::getMainWANInstance();
+ if ( $cache->getQoS( $cache::ATTR_EMULATION ) <= $cache::QOS_EMULATION_SQL ) {
+ // Do not cache RDBMs blobs in...the RDBMs store
+ $ttl = $cache::TTL_UNCACHEABLE;
+ } else {
+ $ttl = $wgRevisionCacheExpiry ?: $cache::TTL_UNCACHEABLE;
+ }
// No negative caching; negative hits on text rows may be due to corrupted replica DBs
return $cache->getWithSetCallback(
- $key = $cache->makeKey( 'revisiontext', 'textid', $this->getTextId() ),
- $wgRevisionCacheExpiry,
+ $cache->makeKey( 'revisiontext', 'textid', $this->getTextId() ),
+ $ttl,
function () {
return $this->fetchText();
},
wfDebugLog( 'caches',
'cluster: ' . get_class( $wgMemc ) .
- ', WAN: ' . $wgMainWANCache .
+ ', WAN: ' . ( $wgMainWANCache === CACHE_NONE ? 'CACHE_NONE' : $wgMainWANCache ) .
', stash: ' . $wgMainStash .
', message: ' . get_class( $messageMemc ) .
', parser: ' . get_class( $parserMemc ) .
return $this->mCascadeRestriction;
}
- /**
- * Loads a string into mRestrictions array
- *
- * @param ResultWrapper $res Resource restrictions as an SQL result.
- * @param string $oldFashionedRestrictions Comma-separated list of page
- * restrictions from page table (pre 1.10)
- */
- private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) {
- $rows = [];
-
- foreach ( $res as $row ) {
- $rows[] = $row;
- }
-
- $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
- }
-
/**
* Compiles list of active page restrictions from both page table (pre 1.10)
* and page_restrictions table for this existing page.
* restrictions from page table (pre 1.10)
*/
public function loadRestrictions( $oldFashionedRestrictions = null ) {
- if ( !$this->mRestrictionsLoaded ) {
- $dbr = wfGetDB( DB_REPLICA );
- if ( $this->exists() ) {
- $res = $dbr->select(
- 'page_restrictions',
- [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
- [ 'pr_page' => $this->getArticleID() ],
- __METHOD__
- );
+ if ( $this->mRestrictionsLoaded ) {
+ return;
+ }
- $this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions );
- } else {
- $title_protection = $this->getTitleProtection();
-
- if ( $title_protection ) {
- $now = wfTimestampNow();
- $expiry = $dbr->decodeExpiry( $title_protection['expiry'] );
-
- if ( !$expiry || $expiry > $now ) {
- // Apply the restrictions
- $this->mRestrictionsExpiry['create'] = $expiry;
- $this->mRestrictions['create'] = explode( ',', trim( $title_protection['permission'] ) );
- } else { // Get rid of the old restrictions
- $this->mTitleProtection = false;
- }
- } else {
- $this->mRestrictionsExpiry['create'] = 'infinity';
+ $id = $this->getArticleID();
+ if ( $id ) {
+ $cache = ObjectCache::getMainWANInstance();
+ $rows = $cache->getWithSetCallback(
+ // Page protections always leave a new null revision
+ $cache->makeKey( 'page-restrictions', $id, $this->getLatestRevID() ),
+ $cache::TTL_DAY,
+ function ( $curValue, &$ttl, array &$setOpts ) {
+ $dbr = wfGetDB( DB_REPLICA );
+
+ $setOpts += Database::getCacheSetOptions( $dbr );
+
+ return iterator_to_array(
+ $dbr->select(
+ 'page_restrictions',
+ [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
+ [ 'pr_page' => $this->getArticleID() ],
+ __METHOD__
+ )
+ );
+ }
+ );
+
+ $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
+ } else {
+ $title_protection = $this->getTitleProtection();
+
+ if ( $title_protection ) {
+ $now = wfTimestampNow();
+ $expiry = wfGetDB( DB_REPLICA )->decodeExpiry( $title_protection['expiry'] );
+
+ if ( !$expiry || $expiry > $now ) {
+ // Apply the restrictions
+ $this->mRestrictionsExpiry['create'] = $expiry;
+ $this->mRestrictions['create'] =
+ explode( ',', trim( $title_protection['permission'] ) );
+ } else { // Get rid of the old restrictions
+ $this->mTitleProtection = false;
}
- $this->mRestrictionsLoaded = true;
+ } else {
+ $this->mRestrictionsExpiry['create'] = 'infinity';
}
+ $this->mRestrictionsLoaded = true;
}
}
/** @var bool Whether this HTTP request is "safe" (even if it is an HTTP post) */
protected $markedAsSafe = false;
+ /**
+ * @codeCoverageIgnore
+ */
public function __construct() {
$this->requestTime = isset( $_SERVER['REQUEST_TIME_FLOAT'] )
? $_SERVER['REQUEST_TIME_FLOAT'] : microtime( true );
* @return array|string Cleaned-up version of the given
* @private
*/
- function normalizeUnicode( $data ) {
+ public function normalizeUnicode( $data ) {
if ( is_array( $data ) ) {
foreach ( $data as $key => $val ) {
$data[$key] = $this->normalizeUnicode( $val );
* @return int
*/
public function getInt( $name, $default = 0 ) {
- return intval( $this->getVal( $name, $default ) );
+ return intval( $this->getRawVal( $name, $default ) );
}
/**
* @return int|null
*/
public function getIntOrNull( $name ) {
- $val = $this->getVal( $name );
+ $val = $this->getRawVal( $name );
return is_numeric( $val )
? intval( $val )
: null;
* @return float
*/
public function getFloat( $name, $default = 0.0 ) {
- return floatval( $this->getVal( $name, $default ) );
+ return floatval( $this->getRawVal( $name, $default ) );
}
/**
* @return bool
*/
public function getBool( $name, $default = false ) {
- return (bool)$this->getVal( $name, $default );
+ return (bool)$this->getRawVal( $name, $default );
}
/**
* @return bool
*/
public function getFuzzyBool( $name, $default = false ) {
- return $this->getBool( $name, $default ) && strcasecmp( $this->getVal( $name ), 'false' ) !== 0;
+ return $this->getBool( $name, $default )
+ && strcasecmp( $this->getRawVal( $name ), 'false' ) !== 0;
}
/**
public function getCheck( $name ) {
# Checkboxes and buttons are only present when clicked
# Presence connotes truth, absence false
- return $this->getVal( $name, null ) !== null;
+ return $this->getRawVal( $name, null ) !== null;
}
/**
* Get the values passed in the query string.
* No transformation is performed on the values.
*
+ * @codeCoverageIgnore
* @return array
*/
public function getQueryValues() {
* Return the contents of the Query with no decoding. Use when you need to
* know exactly what was sent, e.g. for an OAuth signature over the elements.
*
+ * @codeCoverageIgnore
* @return string
*/
public function getRawQueryString() {
}
public function onSubmit( $data ) {
- return $this->page->doPurge();
+ return $this->page->doPurge( WikiPage::PURGE_ALL );
}
public function show() {
$touched = null;
}
+ // If a page was purged on HTTP GET, relect that timestamp to avoid sending 304s
+ $touched = max( $touched, $this->page->getLastPurgeTimestamp() );
+
// Send HTTP 304 if the IMS matches or otherwise set expiry/last-modified headers
if ( $touched && $this->getOutput()->checkLastModified( $touched ) ) {
wfDebug( __METHOD__ . ": done 304\n" );
}
// Try bot passwords
- if ( $authRes === false && $this->getConfig()->get( 'EnableBotPasswords' ) &&
- strpos( $params['name'], BotPassword::getSeparator() ) !== false
+ if (
+ $authRes === false && $this->getConfig()->get( 'EnableBotPasswords' ) &&
+ ( $botLoginData = BotPassword::canonicalizeLoginData( $params['name'], $params['password'] ) )
) {
$status = BotPassword::login(
- $params['name'], $params['password'], $this->getRequest()
+ $botLoginData[0], $botLoginData[1], $this->getRequest()
);
if ( $status->isOK() ) {
$session = $status->getValue();
$authRes = 'Success';
$loginType = 'BotPassword';
- } else {
+ } elseif ( !$botLoginData[2] ) {
$authRes = 'Failed';
$message = $status->getMessage();
LoggerFactory::getInstance( 'authentication' )->info(
* Purges the cache of a page
*/
public function execute() {
+ $main = $this->getMain();
+ if ( !$main->isInternalMode() && !$main->getRequest()->wasPosted() ) {
+ $this->logFeatureUsage( 'purge-via-GET' );
+ $this->setWarning( 'Use of action=purge via GET is deprecated. Use POST instead.' );
+ }
+
$params = $this->extractRequestParams();
$continuationManager = new ApiContinuationManager( $this, [], [] );
ApiQueryBase::addTitleInfo( $r, $title );
$page = WikiPage::factory( $title );
if ( !$user->pingLimiter( 'purge' ) ) {
- $page->doPurge(); // Directly purge and skip the UI part of purge().
+ $flags = WikiPage::PURGE_ALL;
+ if ( !$this->getRequest()->wasPosted() ) {
+ $flags ^= WikiPage::PURGE_GLOBAL_PCACHE; // skip DB_MASTER write
+ }
+ // Directly purge and skip the UI part of purge()
+ $page->doPurge( $flags );
$r['purged'] = true;
} else {
$error = $this->parseMsg( [ 'actionthrottledtext' ] );
return !$this->getUser()->isAllowed( 'purge' );
}
+ protected function getHelpFlags() {
+ $flags = parent::getHelpFlags();
+
+ // Claim that we must be posted for the purposes of help and paraminfo.
+ // @todo Remove this when self::mustBePosted() is updated for T145649
+ if ( !in_array( 'mustbeposted', $flags, true ) ) {
+ $flags[] = 'mustbeposted';
+ }
+
+ return $flags;
+ }
+
public function getAllowedParams( $flags = 0 ) {
$result = [
'forcelinkupdate' => false,
$data['allcentralidlookupproviders'] = $providerIds;
$data['interwikimagic'] = (bool)$config->get( 'InterwikiMagic' );
+ $data['magiclinks'] = $config->get( 'EnableMagicLinks' );
Hooks::run( 'APIQuerySiteInfoGeneralInfo', [ $this, &$data ] );
$limit = $params['limit'];
$result = $this->getResult();
- $extensionDefinedTags = array_fill_keys( ChangeTags::listExtensionDefinedTags(), 0 );
+ $softwareDefinedTags = array_fill_keys( ChangeTags::listSoftwareDefinedTags(), 0 );
$explicitlyDefinedTags = array_fill_keys( ChangeTags::listExplicitlyDefinedTags(), 0 );
- $extensionActivatedTags = array_fill_keys( ChangeTags::listExtensionActivatedTags(), 0 );
+ $softwareActivatedTags = array_fill_keys( ChangeTags::listSoftwareActivatedTags(), 0 );
- $definedTags = array_merge( $extensionDefinedTags, $explicitlyDefinedTags );
+ $definedTags = array_merge( $softwareDefinedTags, $explicitlyDefinedTags );
# Fetch defined tags that aren't past the continuation
if ( $params['continue'] !== null ) {
$tag['hitcount'] = $hitcount;
}
- $isExtension = isset( $extensionDefinedTags[$tagName] );
+ $isSoftware = isset( $softwareDefinedTags[$tagName] );
$isExplicit = isset( $explicitlyDefinedTags[$tagName] );
if ( $fld_defined ) {
- $tag['defined'] = $isExtension || $isExplicit;
+ $tag['defined'] = $isSoftware || $isExplicit;
}
if ( $fld_source ) {
$tag['source'] = [];
- if ( $isExtension ) {
+ if ( $isSoftware ) {
+ // TODO: Can we change this to 'software'?
$tag['source'][] = 'extension';
}
if ( $isExplicit ) {
}
if ( $fld_active ) {
- $tag['active'] = $isExplicit || isset( $extensionActivatedTags[$tagName] );
+ $tag['active'] = $isExplicit || isset( $softwareActivatedTags[$tagName] );
}
$fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $tag );
"api-help-param-deprecated": "Zastaralý.",
"api-help-param-required": "Tento parametr je povinný.",
"api-help-datatypes-header": "Datové typy",
- "api-help-datatypes": "Některé typy parametrů v API potřebují bližší vysvětlení:\n;boolean\n:Booleovské parametry fungují jako zaškrtávací políčka v HTML: pokud je parametr uveden, bez ohledu na hodnotu, je považován za pravdivý. Pro nepravdivou hodnotu parametr zcela vynechte.\n;časová značka\n:Časové značky lze uvádět v několika formátech. Doporučuje se datum a čas podle ISO 8601. Všechny časy jsou v UTC a obsažené časové pásmo je ignorováno.\n:* Datum a čas podle ISO 8601, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>Z</kbd> (interpunkce a <kbd>Z</kbd> jsou nepovinné)\n:* Datum a čas podle ISO 8601 s (ignorovaným) zlomkem sekundy, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>.<var>00001</var>Z</kbd> (pomlčky, dvojtečky a <kbd>Z</kbd> jsou nepovinné)\n:* Formát MediaWiki, <kbd><var>2001</var><var>01</var><var>15</var><var>14</var><var>56</var><var>00</var></kbd>\n:* Obecný číselný formát, <kbd><var>2001</var>-<var>01</var>-<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd> (nepovinné časové pásmo <kbd>GMT</kbd>, <kbd>+<var>##</var></kbd> nebo <kbd>-<var>##</var></kbd> se ignoruje)\n:* Formát EXIF, <kbd><var>2001</var>:<var>01</var>:<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Formát podle RFC 2822 (časové pásmo lze vynechat), <kbd><var>Mon</var>, <var>15</var> <var>Jan</var> <var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Formát podle RFC 850 (časové pásmo lze vynechat), <kbd><var>Monday</var>, <var>15</var>-<var>Jan</var>-<var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Formát podle céčkové funkce ctime, <kbd><var>Mon</var> <var>Jan</var> <var>15</var> <var>14</var>:<var>56</var>:<var>00</var> <var>2001</var></kbd>\n:* Sekundy od 1970-01-01T00:00:00Z jako celé číslo o 1–13 číslicích (s výjimkou <kbd>0</kbd>)\n:* Řetězec <kbd>now</kbd>",
+ "api-help-datatypes": "Vstupem do MediaWiki by mělo být UTF-8 normalizované do NFC. Jiný vstup se MediaWiki může pokusit převést, ale tím se může stát, že některé operace (např. [[Special:ApiHelp/edit|editace]] s kontrolou MD5) selžou.\n\nNěkteré typy parametrů v API potřebují bližší vysvětlení:\n;boolean\n:Booleovské parametry fungují jako zaškrtávací políčka v HTML: pokud je parametr uveden, bez ohledu na hodnotu, je považován za pravdivý. Pro nepravdivou hodnotu parametr zcela vynechte.\n;časová značka\n:Časové značky lze uvádět v několika formátech. Doporučuje se datum a čas podle ISO 8601. Všechny časy jsou v UTC a obsažené časové pásmo je ignorováno.\n:* Datum a čas podle ISO 8601, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>Z</kbd> (interpunkce a <kbd>Z</kbd> jsou nepovinné)\n:* Datum a čas podle ISO 8601 s (ignorovaným) zlomkem sekundy, <kbd><var>2001</var>-<var>01</var>-<var>15</var>T<var>14</var>:<var>56</var>:<var>00</var>.<var>00001</var>Z</kbd> (pomlčky, dvojtečky a <kbd>Z</kbd> jsou nepovinné)\n:* Formát MediaWiki, <kbd><var>2001</var><var>01</var><var>15</var><var>14</var><var>56</var><var>00</var></kbd>\n:* Obecný číselný formát, <kbd><var>2001</var>-<var>01</var>-<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd> (nepovinné časové pásmo <kbd>GMT</kbd>, <kbd>+<var>##</var></kbd> nebo <kbd>-<var>##</var></kbd> se ignoruje)\n:* Formát EXIF, <kbd><var>2001</var>:<var>01</var>:<var>15</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Formát podle RFC 2822 (časové pásmo lze vynechat), <kbd><var>Mon</var>, <var>15</var> <var>Jan</var> <var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Formát podle RFC 850 (časové pásmo lze vynechat), <kbd><var>Monday</var>, <var>15</var>-<var>Jan</var>-<var>2001</var> <var>14</var>:<var>56</var>:<var>00</var></kbd>\n:* Formát podle céčkové funkce ctime, <kbd><var>Mon</var> <var>Jan</var> <var>15</var> <var>14</var>:<var>56</var>:<var>00</var> <var>2001</var></kbd>\n:* Sekundy od 1970-01-01T00:00:00Z jako celé číslo o 1–13 číslicích (s výjimkou <kbd>0</kbd>)\n:* Řetězec <kbd>now</kbd>\n;alternativní oddělovač vícenásobných hodnot\n:Parametry, které přijímají několik hodnot, se zpravidla předávají s hodnotami oddělenými svislítkem, např. <kbd>param=hodnota1|hodnota2</kbd> nebo <kbd>param=hodnota1%7Chodnota2</kbd>. Pokud musí hodnota obsahovat svislítko, použijte jako oddělovač znak U+001F (Unit Separator) ''a'' před hodnotu přidejte U+001F, např. <kbd>param=%1Fhodnota1%1Fhodnota2</kbd>.",
"api-help-param-type-integer": "Typ: {{PLURAL:$1|1=celé číslo|2=seznam celých čísel}}",
"api-help-param-type-boolean": "Typ: boolean ([[Special:ApiHelp/main#main/datatypes|podrobnosti]])",
- "api-help-param-list": "{{PLURAL:$1|1=Jedna z následujících hodnot|2=Hodnoty (oddělené <kbd>{{!}}</kbd>)}}: $2",
+ "api-help-param-list": "{{PLURAL:$1|1=Jedna z následujících hodnot|2=Hodnoty (oddělené <kbd>{{!}}</kbd> nebo [[Special:ApiHelp/main#main/datatypes|alternativou]].)}}: $2",
"api-help-param-list-can-be-empty": "{{PLURAL:$1|0=Musí být prázdné|Může být prázdné nebo $2}}",
"api-help-param-limit": "Není dovoleno více než $1.",
"api-help-param-limit2": "Není dovoleno více než $1 ($2 pro boty).",
"api-help-param-integer-max": "{{PLURAL:$1|1=Hodnota nesmí|2=Hodnoty nesmějí}} být vyšší než $3.",
"api-help-param-integer-minmax": "{{PLURAL:$1|1=Hodnota|2=Hodnoty}} musí ležet mezi $2 a $3.",
"api-help-param-upload": "Musí se odeslat POST požadavkem jako načítaný soubor pomocí multipart/form-data.",
- "api-help-param-multi-separate": "Hodnoty oddělujte pomocí <kbd>|</kbd>.",
+ "api-help-param-multi-separate": "Hodnoty oddělujte pomocí <kbd>|</kbd> nebo [[Special:ApiHelp/main#main/datatypes|alternativou]].",
"api-help-param-multi-max": "Maximální počet hodnot je {{PLURAL:$1|$1}} (pro boty {{PLURAL:$2|$2}}).",
"api-help-param-default": "Implicitní hodnota: $1",
"api-help-param-default-empty": "Implicitní hodnota: <span class=\"apihelp-empty\">(prázdné)</span>",
"api-help-permissions": "{{PLURAL:$1|Oprávnění}}:",
"api-help-permissions-granted-to": "Uděleno {{PLURAL:$1|skupině|skupinám}}: $2",
"api-help-right-apihighlimits": "Používání vyšších limitů v API dotazech (pomalé dotazy: $1, rychlé dotazy: $2). Limity pro pomalé dotazy se vztahují i na vícehodnotové parametry.",
+ "api-help-open-in-apisandbox": "<small>[otevřít v pískovišti]</small>",
"api-credits-header": "Zásluhy",
"api-credits": "Vývojáři API:\n* Roan Kattouw (hlavní vývojář září 2007–2009)\n* Viktor Vasiljev\n* Bryan Tong Minh\n* Sam Reed\n* Jurij Astrachan (tvůrce, hlavní vývojář září 2006–září 2007)\n* Brad Jorsch (hlavní vývojář od 2013)\n\nSvé komentáře, návrhy či dotazy posílejte na mediawiki-api@lists.wikimedia.org\nnebo založte chybové hlášení na https://phabricator.wikimedia.org/."
}
"Copper12"
]
},
- "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Documentación]]\n* [[mw:API:FAQ|Preguntas frecuentes]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Lista de correos]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API de anuncios]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Errores y peticiones]\n</div>\n<strong>Estado:</strong> Todas las características que se muestran en esta página debería funcionar, pero la API aún está en desarrollo activo y puede cambiar en cualquier momento. Suscríbete a [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ la lista de correo de mediawiki-api-announce] para estar al día de las actualizaciones.\n\n<strong>Solicitudes erróneas:</strong> Cuando se envían solicitudes erróneas a la API, se envía un encabezado HTTP con la clave \"MediaWiki-API-Error\" y ambos valores, del encabezado y el código de error, se establecerán en el mismo valor. Para más información, véase [[mw:API:Errors_and_warnings|API: Errores y advertencias]].\n\n<strong>Pruebas:</strong> para facilitar las pruebas de solicitudes a la API, consulta [[Special:ApiSandbox]].",
+ "apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Documentación]]\n* [[mw:API:FAQ|Preguntas frecuentes]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Lista de correo]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Anuncios de la API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Errores y peticiones]\n</div>\n<strong>Estado:</strong> Todas las características que se muestran en esta página deberían funcionar, pero la API aún se encuentra en desarrollo activo y puede cambiar en cualquier momento. Suscríbete a [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ la lista de correo de mediawiki-api-announce] para estar al día de las actualizaciones.\n\n<strong>Solicitudes erróneas:</strong> Cuando se envían solicitudes erróneas a la API, se envía una cabecera HTTP con la clave \"MediaWiki-API-Error\". El valor de la cabecera y el código de error devuelto tomarán el mismo valor. Para más información, véase [[mw:API:Errors_and_warnings|API: Errores y advertencias]].\n\n<strong>Pruebas:</strong> Para facilitar las pruebas de solicitudes a la API, consulta [[Special:ApiSandbox]].",
"apihelp-main-param-action": "Qué acción se realizará.",
"apihelp-main-param-format": "El formato de la salida.",
- "apihelp-main-param-maxlag": "El máximo retraso puede ser utilizado cuando MediaWiki está instalado en una base de datos replicada clúster. Para guardar las acciones que causan más de replicación de sitios de retraso, este parámetro puede hacer que el cliente espere hasta que el retraso de la replicación es menor que el valor especificado. En caso de exceso de lag, código de error <samp>maxlag</samp> se devuelve con un mensaje parecido a <samp>la Espera de $host: $lag segundos quedado</samp>.<br />Véase [[mw:Manual:Maxlag_parameter|Manual: Maxlag parámetro]] para más información.",
+ "apihelp-main-param-maxlag": "El retraso (lag) máximo puede ser utilizado cuando MediaWiki está instalado en un conjunto de bases de datos replicadas. Para evitar cualquier acción que pudiera causar un retraso aún mayor en la replicación del sitio, este parámetro puede causar que el cliente espere hasta que el retraso de replicación sea menor que el valor especificado. En caso de exceso de retraso, se devuelve un código de error <samp>maxlag</samp> con un mensaje similar a <samp>Esperando a $host: $lag segundos de retraso</samp>.<br />Véase [[mw:Manual:Maxlag_parameter|Manual:Parámetro maxlag]] para más información.",
"apihelp-main-param-smaxage": "Establece el encabezado HTTP <code>s-maxage</code> de control de caché a esta cantidad de segundos. Los errores nunca se almacenan en caché.",
"apihelp-main-param-maxage": "Establece el encabezado HTTP <code>max-age</code> de control de caché a esta cantidad de segundos. Los errores nunca se almacenan en caché.",
"apihelp-main-param-assert": "Comprobar que el usuario haya iniciado sesión si el valor es <kbd>user</kbd> o si tiene el permiso de bot si es <kbd>bot</kbd>.",
"apihelp-main-param-requestid": "Cualquier valor dado aquí se incluirá en la respuesta. Se puede utilizar para distinguir solicitudes.",
"apihelp-main-param-servedby": "Incluir el nombre del host que ha servido la solicitud en los resultados.",
"apihelp-main-param-curtimestamp": "Incluir la marca de tiempo actual en el resultado.",
- "apihelp-main-param-origin": "Cuando se accede a la API usando una petición AJAX de distinto dominio (CORS), se establece este valor al dominio de origen. Debe ser incluido en cualquier petición pre-vuelo, y por lo tanto debe ser parte de la URI de la petición (no del cuerpo POST). Debe coincidir exactamente con uno de los orígenes de la cabecera <code>Origin</code>, por lo que debería ser algo como <kbd>https://en.wikipedia.org</kbd> o <kbd>https://meta.wikimedia.org</kbd>. Si este parámetro no coincide con la cabecera <code>Origin</code>, se devolverá una respuesta 403.\nSi este parámetro coincide con la cabecera <code>Origin</code> y el origen está en lista blanca, se creará una cabecera <code>Access-Control-Allow-Origin</code>.",
- "apihelp-main-param-uselang": "El idioma que se usará para las traducciones de mensajes. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> con <kbd>siprop=languages</kbd> devuelve una lista de códigos de idiomas, o especifica <kbd>user</kbd> para usar la preferencia de idioma del usuario actual, o especifica <kbd>content</kbd> para usar el idioma de contenido de este wiki.",
+ "apihelp-main-param-origin": "Cuando se accede a la API usando una petición AJAX de distinto dominio (CORS), se establece este valor al dominio de origen. Debe ser incluido en cualquier petición pre-vuelo, y por lo tanto debe ser parte de la URI de la petición (no del cuerpo POST).\n\nEn las peticiones con autenticación, debe coincidir exactamente con uno de los orígenes de la cabecera <code>Origin</code>, por lo que debería ser algo como <kbd>https://en.wikipedia.org</kbd> o <kbd>https://meta.wikimedia.org</kbd>. Si este parámetro no coincide con la cabecera <code>Origin</code>, se devolverá una respuesta 403. Si este parámetro coincide con la cabecera <code>Origin</code> y el origen está en la lista blanca, se creará una cabecera <code>Access-Control-Allow-Origin</code>.\n\nEn las peticiones sin autenticación, introduce el valor <kbd>*</kbd>. Esto creará una cabecera <code>Access-Control-Allow-Origin</code>, pero el valor de <code>Access-Control-Allow-Credentials</code> será <code>false</code> y todos los datos que dependan del usuario estarán restringidos.",
+ "apihelp-main-param-uselang": "El idioma que se utilizará para las traducciones de mensajes. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> con <kbd>siprop=languages</kbd> devuelve una lista de códigos de idiomas. También puedes introducir <kbd>user</kbd> para usar la preferencia de idioma del usuario actual, o <kbd>content</kbd> para usar el idioma de contenido de este wiki.",
"apihelp-block-description": "Bloquear a un usuario.",
"apihelp-block-param-user": "El nombre de usuario, dirección IP o intervalo de IP que quieres bloquear.",
"apihelp-block-param-expiry": "Fecha de expiración. Puede ser relativa (por ejemplo, <kbd>5 months</kbd> o <kbd>2 weeks</kbd>) o absoluta (por ejemplo, <kbd>2014-09-18T12:34:56Z</kbd>). Si se establece en <kbd>infinite</kbd>, <kbd>indefinite</kbd>, o <kbd>never</kbd>, el bloqueo será permanente.",
"apihelp-watch-example-watch": "Vigilar la página <kbd>Main Page</kbd>.",
"apihelp-watch-example-unwatch": "Dejar de vigilar la <kbd>Main Page</kbd>.",
"apihelp-format-example-generic": "Devolver el resultado de la consulta en formato $1.",
+ "apihelp-json-description": "Extraer los datos de salida en formato JSON.",
+ "apihelp-json-param-callback": "Si se especifica, envuelve la salida dentro de una llamada a una función dada. Por motivos de seguridad, cualquier dato específico del usuario estará restringido.",
+ "apihelp-json-param-utf8": "Si se especifica, codifica la mayoría (pero no todos) de los caracteres no pertenecientes a ASCII como UTF-8 en lugar de reemplazarlos por secuencias de escape hexadecimal. Toma el comportamiento por defecto si <var>formatversion</var> no es <kbd>1</kbd>.",
+ "apihelp-json-param-ascii": "Si se especifica, codifica todos los caracteres no pertenecientes a ASCII mediante secuencias de escape hexadecimal. Toma el comportamiento por defecto si <var>formatversion</var> no es <kbd>1</kbd>.",
+ "apihelp-json-param-formatversion": "Formato de salida:\n;1: Formato retrocompatible (booleanos con estilo XML, claves <samp>*</samp> para nodos de contenido, etc.).\n;2: Formato moderno experimental. ¡Atención, las especificaciones pueden cambiar!\n;latest: Utiliza el último formato (actualmente <kbd>2</kbd>). Puede cambiar sin aviso.",
+ "apihelp-none-description": "No extraer nada.",
+ "apihelp-php-description": "Extraer los datos de salida en formato serializado PHP.",
+ "apihelp-rawfm-description": "Extraer los datos de salida, incluidos los elementos de depuración, en formato JSON (embellecido en HTML).",
+ "apihelp-xml-param-xslt": "Si se especifica, añade la página nombrada como una hoja de estilo XSL. El valor debe ser un título en el espacio de nombres {{ns:mediawiki}} que termine en <code>.xsl</code>.",
"api-help-main-header": "Módulo principal",
"api-help-flag-deprecated": "Este módulo está en desuso.",
"api-help-flag-readrights": "Este módulo requiere permisos de lectura.",
"apihelp-options-example-change": "Modifier les préférences <kbd>skin</kbd> et <kbd>hideminor</kbd>.",
"apihelp-options-example-complex": "Réinitialiser toutes les préférences, puis définir <kbd>skin</kbd> et <kbd>nickname</kbd>.",
"apihelp-paraminfo-description": "Obtenir des informations sur les modules de l’API.",
- "apihelp-paraminfo-param-modules": "Liste des noms de module (valeurs des paramètres <var>action</var> et <var>format</var>, ou <kbd>main</kbd>). Peut spécifier des sous-modules avec un <kbd>+</kbd>, ou tous les sous-modules avec <kbd>+*</kbd>, ou tousles sous-modules récursivement avec <kbd>+**</kbd>.",
+ "apihelp-paraminfo-param-modules": "Liste des noms de module (valeurs des paramètres <var>action</var> et <var>format</var>, ou <kbd>main</kbd>). Peut spécifier des sous-modules avec un <kbd>+</kbd>, ou tous les sous-modules avec <kbd>+*</kbd>, ou tous les sous-modules récursivement avec <kbd>+**</kbd>.",
"apihelp-paraminfo-param-helpformat": "Format des chaînes d’aide.",
"apihelp-paraminfo-param-querymodules": "Liste des noms de module de requêtage (valeur des paramètres <var>prop</var>, <var>meta</var> ou <var>list</var>=). Utiliser <kbd>$1modules=query+foo</kbd> au lieu de <kbd>$1querymodules=foo</kbd>.",
"apihelp-paraminfo-param-mainmodule": "Obtenir aussi des informations sur le module principal (niveau supérieur). Utiliser plutôt <kbd>$1modules=main</kbd>.",
"apihelp-expandtemplates-paramvalue-prop-properties": "Propietæ da paggina definie da-e paole magiche esteise into wikitesto.",
"apihelp-expandtemplates-paramvalue-prop-volatile": "Se l'output o segge volatile e o no 'agge da ese riadoeuviou atr'onde a l'interno da paggina.",
"apihelp-expandtemplates-paramvalue-prop-ttl": "O tempo mascimo doppo o quæ e memoizaçioin tempoannie (cache) do risultou dovieivan ese invalidæ.",
- "apihelp-feedcontributions-param-feedformat": "O formato do feed."
+ "apihelp-feedcontributions-param-feedformat": "O formato do feed.",
+ "apihelp-feedrecentchanges-param-feedformat": "O formato do feed.",
+ "apihelp-feedrecentchanges-param-namespace": "Namespace a-o quæ limitâ i risultæ.",
+ "apihelp-feedrecentchanges-param-associated": "Inciodi namespace associou (discuscion ò prinçipâ)",
+ "apihelp-feedrecentchanges-param-days": "Intervallo de giorni pe-i quæ limitâ i risultæ.",
+ "apihelp-feedrecentchanges-param-limit": "Nummero mascimo di risultæ da restituî.",
+ "apihelp-feedrecentchanges-param-from": "Mostra i cangiamenti da alloa.",
+ "apihelp-feedrecentchanges-param-hideminor": "Ascondi e modiffiche menoî.",
+ "apihelp-feedrecentchanges-param-hidebots": "Ascondi e modiffiche fæte da di bot.",
+ "apihelp-feedrecentchanges-param-hideanons": "Ascondi e modiffiche fæte da di utenti anonnimi.",
+ "apihelp-feedrecentchanges-param-hideliu": "Ascondi e modiffiche fæte da-i utenti registræ.",
+ "apihelp-feedrecentchanges-param-hidepatrolled": "Ascondi e modiffiche veificæ.",
+ "apihelp-feedrecentchanges-param-hidemyself": "O l'asconde e modiffiche fæte da l'utente attoale.",
+ "apihelp-feedrecentchanges-param-hidecategorization": "Ascondi e variaçioin d'apartegninça a-e categorie.",
+ "apihelp-feedrecentchanges-param-tagfilter": "Filtra pe etichetta.",
+ "apihelp-feedrecentchanges-param-target": "Mostra solo e modifiche a-e paggine collegæ da questa paggina.",
+ "apihelp-feedrecentchanges-param-showlinkedto": "Fanni védde sôlo i cangiaménti a-e pàggine colegæ a-a quella speçificâ",
+ "apihelp-feedrecentchanges-param-categories": "Mostra solo e variaçioin in sce-e paggine de tutte queste categorie.",
+ "apihelp-feedrecentchanges-param-categories_any": "Mostra invece solo e variaçioin in sce-e paggine inte 'na qualonque categoria.",
+ "apihelp-feedrecentchanges-example-simple": "Mostra i urtime modiffiche.",
+ "apihelp-feedrecentchanges-example-30days": "Mostra e modifiche di urtimi 30 giorni.",
+ "apihelp-feedwatchlist-param-feedformat": "O formato do feed.",
+ "apihelp-feedwatchlist-param-hours": "Elenca e paggine modificæ inte quest'urtime oe.",
+ "apihelp-feedwatchlist-param-linktosections": "Collega direttamente a-e seçioin modificæ, se poscibbile.",
+ "apihelp-feedwatchlist-example-all6hrs": "Mostra tutte e modiffiche a-e pagine oservæ inti urtime 6 oe.",
+ "apihelp-filerevert-description": "Ripristina un file a 'na verscion precedente.",
+ "apihelp-filerevert-param-filename": "Nomme do file de destinaçion, sença o prefisso 'File:'.",
+ "apihelp-filerevert-param-comment": "Commento in sciô caregamento.",
+ "apihelp-filerevert-param-archivename": "Nomme de l'archivvio da verscion da ripristinâ.",
+ "apihelp-filerevert-example-revert": "Ripristina <kbd>Wiki.png</kbd> a-a verscion do <kbd>2011-03-05T15:27:40Z</kbd>.",
+ "apihelp-help-description": "Mostra a guidda pe-i modduli speçificæ.",
+ "apihelp-help-param-toc": "Inciodi un endexo inte l'output HTML.",
+ "apihelp-help-example-main": "Agiutto pe-o moddulo prinçipâ.",
+ "apihelp-help-example-submodules": "Agiutto pe <kbd>action=query</kbd> e tutti i so sotto-modduli.",
+ "apihelp-help-example-recursive": "Tutti i agiutti inte 'na paggina.",
+ "apihelp-help-example-help": "Agiutto pe-o moddulo d'agiutto mæximo.",
+ "apihelp-imagerotate-description": "Roeua un-a o ciù inmaggine.",
+ "apihelp-imagerotate-param-rotation": "Graddi de rotaçion de l'inmaggine in senso oaio.",
+ "apihelp-imagerotate-example-simple": "Roeua <kbd>File:Example.png</kbd> de <kbd>90</kbd> graddi.",
+ "apihelp-imagerotate-example-generator": "Roeua tutte e inmaggine in <kbd>Category:Flip</kbd> de <kbd>180</kbd> graddi.",
+ "apihelp-import-param-summary": "Ogetto into registro d'importaçion.",
+ "apihelp-import-param-xml": "File XML caregou.",
+ "apihelp-import-param-interwikisource": "Pe-e importaçioin interwiki: wiki da-e quæ importâ.",
+ "apihelp-import-param-interwikipage": "Pe-e importaçioin interwiki: paggina da importâ.",
+ "apihelp-import-param-fullhistory": "Pe-e importaçioin interwiki: importa l'intrega cronologia, non solo a verscion attoale.",
+ "apihelp-import-param-templates": "Pe-e importaçioin interwiki: importa tutti i template incioxi ascì.",
+ "apihelp-import-param-namespace": "Importa inte questo namespace. O no poeu ese doeuviou insemme a <var>$1rootpage</var>.",
+ "apihelp-import-param-rootpage": "Importa comme sottopaggina de questa paggina. O no poeu ese doeuviou insemme a <var>$1namespace</var>.",
+ "apihelp-import-example-import": "Importa [[meta:Help:ParserFunctions]] into namespace 100 con cronologia completa.",
+ "apihelp-linkaccount-description": "Colegamento de 'n'utença de 'n provider de terçe parte a l'utente corente.",
+ "apihelp-linkaccount-example-link": "Avvia o processo de collegamento a 'n'utença da <kbd>Example</kbd>.",
+ "apihelp-login-description": "Accedi e otegni i cookie d'aotenticaçion.\n\nQuest'açion dev'ese doeuviâ escluxivamente in combinaçion con [[Special:BotPasswords]]; doeuviâla pe l'accesso a l'account prinçipâ o l'è deprecou e o poeu fallî sença preaviso. Pe acedere in moddo seguo a l'utença prinçipâ, doeuvia <kbd>[[Special:ApiHelp/clientlogin|action=clientlogin]]</kbd>.",
+ "apihelp-login-description-nobotpasswords": "Accedi e otegni i cookies d'aotenticaçion.\n\nQuest'açion a l'è deprecâ e a poeu fallî sença preaviso. Pe acede in moddo seguo, doeuvia [[Special:ApiHelp/clientlogin|action=clientlogin]].",
+ "apihelp-login-param-name": "Nomme utente.",
+ "apihelp-login-param-password": "Password.",
+ "apihelp-login-param-domain": "Dominnio (opçionâ).",
+ "apihelp-login-example-gettoken": "Recuppera un token de login.",
+ "apihelp-login-example-login": "Intra",
+ "apihelp-logout-description": "Sciorti e scassa i dæti da sescion.",
+ "apihelp-logout-example-logout": "Disconnetti l'utente attoale.",
+ "apihelp-mergehistory-description": "O l'unisce e cronologie de paggine.",
+ "apihelp-mergehistory-param-from": "O tittolo da paggina da-a quæ a cronologia a saiâ unia. O no poeu ese doeuviou insemme a <var>$1fromid</var>.",
+ "apihelp-mergehistory-param-fromid": "L'ID da paggina da-a quæ a cronologia a saiâ unia. O no poeu ese doeuviou insemme a <var>$1from</var>.",
+ "apihelp-mergehistory-param-to": "O tittolo da paggina inta quæ a cronologia a saiâ unia. O no poeu ese doeuviou insemme a <var>$1toid</var>.",
+ "apihelp-mergehistory-param-toid": "L'ID da paggina inta quæ a cronologia a saiâ unia. O no poeu ese doeuviou insemme a <var>$1fromid</var>.",
+ "apihelp-mergehistory-param-timestamp": "O timestamp scin a-o quæle verscioin saian mesciæ da-a cronologia da paggina d'origine a quella da paggina de destinaçion. Se omisso, l'intrega cronologia da paggina d'origine a saiâ unia inta paggina de destinaçion.",
+ "apihelp-mergehistory-param-reason": "Raxon pe l'union da cronologia.",
+ "apihelp-mergehistory-example-merge": "Unisci l'intrega cronologia de <kbd>Oldpage</kbd> inte <kbd>Newpage</kbd>.",
+ "apihelp-mergehistory-example-merge-timestamp": "Unisci e verscioin da paggina <kbd>Oldpage</kbd> scin a <kbd>2015-12-31T04:37:41Z</kbd> inte <kbd>Newpage</kbd>.",
+ "apihelp-move-description": "Mescia 'na paggina",
+ "apihelp-move-param-from": "Tittolo da paggina da rinominâ. O no poeu vese doeuviou insemme a <var>$1pageid</var>.",
+ "apihelp-move-param-fromid": "ID de paggina da paggina da rinominâ. O no poeu ese doeuviou insemme a <var>$1title</var>.",
+ "apihelp-move-param-to": "Tittolo a-o quæ mesciâ a paggina.",
+ "apihelp-move-param-reason": "Raxon da rinommina.",
+ "apihelp-move-param-movetalk": "Rinommina a paggina de discuscion, s'a l'existe.",
+ "apihelp-move-param-movesubpages": "Rinommina e sottopaggine, se applicabile.",
+ "apihelp-move-param-noredirect": "No creâ un rinvio.",
+ "apihelp-move-param-watch": "Azonze a paggina e o redirect a-i oservæ speciali de l'utente attoale.",
+ "apihelp-move-param-unwatch": "Rimoeuvi a paggina e o redirect da-i oservæ speciali de l'utente attoale.",
+ "apihelp-move-param-ignorewarnings": "Ignora i messaggi d'avvertimento do scistema",
+ "apihelp-move-example-move": "Mescia <kbd>Badtitle</kbd> a <kbd>Goodtitle</kbd> sença lasciâ de redirect.",
+ "apihelp-opensearch-param-search": "Stringa de çerchia.",
+ "apihelp-opensearch-param-limit": "Nummero mascimo di risultæ da restituî.",
+ "apihelp-opensearch-param-suggest": "No stanni a fâ ninte se <var>[[mw:Manual:$wgEnableOpenSearchSuggest|$wgEnableOpenSearchSuggest]]</var> o l'è faso.",
+ "apihelp-opensearch-param-format": "O formato de l'output.",
+ "apihelp-opensearch-example-te": "Troeuva e paggine che començan con <kbd>Te</kbd>.",
+ "apihelp-options-example-reset": "Reimposta tutte e preferençe.",
+ "apihelp-paraminfo-description": "Otegni de informaçioin in scî modduli API.",
+ "apihelp-paraminfo-param-helpformat": "Formato de stringhe d'agiutto.",
+ "apihelp-parse-param-summary": "Ogetto da analizâ.",
+ "apihelp-query+allcategories-param-prop": "Quæ propietæ otegnî:",
+ "apihelp-query+allcategories-paramvalue-prop-size": "Azonzi o nummero de paggine inta categoria.",
+ "apihelp-query+allcategories-paramvalue-prop-hidden": "Etichetta e categorie che son ascose con <code>__HIDDENCAT__</code>.",
+ "apihelp-query+allcategories-example-size": "Elenca e categorie con de informaçioin in sciô numero de paggine in ciascun-a.",
+ "apihelp-query+alldeletedrevisions-description": "Elenca tutte e verscioin scassæ da 'n utente ò inte 'n namespace.",
+ "apihelp-query+alldeletedrevisions-paraminfo-useronly": "O poeu ese doeuviou solo con <var>$3user</var>.",
+ "apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "O no poeu ese doeuviou con <var>$3user</var>.",
+ "apihelp-query+alldeletedrevisions-param-start": "O timestamp da-o quæ començâ l'elenco.",
+ "apihelp-query+alldeletedrevisions-param-end": "O timestamp a-o quæ interrompî l'elenco.",
+ "apihelp-query+alldeletedrevisions-param-from": "Comença l'elenco a questo tittolo.",
+ "apihelp-query+alldeletedrevisions-param-to": "Interrompi l'elenco a questo titolo.",
+ "apihelp-query+alldeletedrevisions-param-prefix": "Riçerca pe tutti i titoli de pagine che començan con questo valô.",
+ "apihelp-query+alldeletedrevisions-param-user": "Elenca solo e verscioin de questo utente.",
+ "apihelp-query+alldeletedrevisions-param-excludeuser": "No elencâ e verscioin de questo utente.",
+ "apihelp-query+alldeletedrevisions-param-namespace": "Elenca solo e paggine inte questo namespace.",
+ "apihelp-query+alldeletedrevisions-example-user": "Elenca i urtimi 50 contributi scassæ de l'utente <kbd>Example</kbd>.",
+ "apihelp-query+alldeletedrevisions-example-ns-main": "Elenca e primme 50 verscioin scassæ into namespace prinçipâ.",
+ "apihelp-query+allfileusages-param-from": "O titolo do file da-o quæ començâ l'elenco.",
+ "apihelp-query+allfileusages-param-to": "O tittolo do file a-o quæ interrompî l'elenco.",
+ "apihelp-query+allfileusages-param-prefix": "Riçerca pe tutti i titoli di file che començan con questo valô.",
+ "apihelp-query+allfileusages-paramvalue-prop-title": "O l'azonze o tittolo do file.",
+ "apihelp-query+allfileusages-param-limit": "Quanti elementi totali restitoî.",
+ "apihelp-query+allfileusages-param-dir": "A direçion inta quæ elencâ.",
+ "apihelp-query+allfileusages-example-generator": "Otegni e paggine contegninte i file.",
+ "apihelp-query+allimages-param-sort": "Propietæ d'amerçamento.",
+ "apihelp-query+allimages-param-dir": "A direçion inta quæ elencâ.",
+ "apihelp-query+allimages-param-from": "O titolo de l'inmagine da-a quæ començâ l'elenco. O poeu ese doeuviou solo con $1sort=name."
}
"apihelp-managetags-param-reason": "Opcjonalny powód utworzenia, usunięcia, włączenia lub wyłączenia znacznika.",
"apihelp-managetags-param-ignorewarnings": "Czy zignorować ostrzeżenia, które pojawiają się w trakcie operacji.",
"apihelp-mergehistory-description": "Łączenie historii edycji.",
+ "apihelp-mergehistory-param-reason": "Powód łączenia historii.",
"apihelp-move-description": "Przenieś stronę.",
"apihelp-move-param-reason": "Powód zmiany nazwy.",
"apihelp-move-param-movetalk": "Zmień nazwę strony dyskusji, jeśli istnieje.",
"Iniquity",
"Лилиә",
"Айсар",
- "Гизатуллина"
+ "Гизатуллина",
+ "MaxSem"
]
},
"apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Документация]]\n* [[mw:API:FAQ|ЧаВО]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Почтовая рассылка]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Новости API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Ошибки и запросы]\n</div>\n<strong>Статус:</strong> Все отображаемые на этой странице функции должны работать, однако API находится в статусе активной разработки и может измениться в любой момент. Подпишитесь на [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ почтовую рассылку mediawiki-api-announce], чтобы быть в курсе обновлений.\n\n<strong>Ошибочные запросы:</strong> Если API получает запрос с ошибкой, вернётся заголовок HTTP с ключом «MediaWiki-API-Error», после чего значение заголовка и код ошибки будут отправлены обратно и установлены в то же значение. Более подробную информацию см. [[mw:API:Errors_and_warnings|API: Ошибки и предупреждения]].\n\n<strong>Тестирование:</strong> для удобства тестирования API-запросов, см. [[Special:ApiSandbox]].",
"apihelp-main-param-requestid": "Любое заданное здесь значение будет включено в ответ. Может быть использовано для различения запросов.",
"apihelp-main-param-servedby": "Включить в результаты имя хоста, обработавшего запрос.",
"apihelp-main-param-curtimestamp": "Включить в результаты временную метку.",
- "apihelp-main-param-origin": "При обращении к API, используя кросс-доменный AJAX-запрос (CORS), задайте параметру значение исходного домена. Он должен быть включён в любой предварительный запрос и таким образом должен быть частью URI-запроса (не тела POST).\n\nДля аутентифицированных запросов он должен точно соответствовать одному из источников в заголовке <code>Origin<code>, так что он должен быть задан наподобие <kbd>https://ru.wikipedia.org</kbd> или <kbd>https://meta.wikimedia.org</kbd>. Если параметр не соответствует заголовку <code>Origin<code>, будет возвращён ответ с кодом ошибки 403. Если параметр соответствует заголовку <code>Origin</code>, и источник находится в белом списке, будут установлены заголовки <code>Access-Control-Allow-Origin</code> и <code>Access-Control-Allow-Credentials</code>.\n\nДля неаутентифицированных запросов укажите значение <kbd>*</kbd>. Это приведёт к установке заголовка <code>Access-Control-Allow-Origin</code> заголовка должен быть установлен, но <code>Access-Control-Allow-Credentials</code> примет значение <code>false</code> и все пользовательские данные будут ограничены.",
+ "apihelp-main-param-origin": "При обращении к API, используя кросс-доменный AJAX-запрос (CORS), задайте параметру значение исходного домена. Он должен быть включён в любой предварительный запрос и таким образом должен быть частью URI-запроса (не тела POST).\n\nДля аутентифицированных запросов он должен точно соответствовать одному из источников в заголовке <code>Origin</code>, так что он должен быть задан наподобие <kbd>https://ru.wikipedia.org</kbd> или <kbd>https://meta.wikimedia.org</kbd>. Если параметр не соответствует заголовку <code>Origin</code>, будет возвращён ответ с кодом ошибки 403. Если параметр соответствует заголовку <code>Origin</code>, и источник находится в белом списке, будут установлены заголовки <code>Access-Control-Allow-Origin</code> и <code>Access-Control-Allow-Credentials</code>.\n\nДля неаутентифицированных запросов укажите значение <kbd>*</kbd>. Это приведёт к установке заголовка <code>Access-Control-Allow-Origin</code> заголовка должен быть установлен, но <code>Access-Control-Allow-Credentials</code> примет значение <code>false</code> и все пользовательские данные будут ограничены.",
"apihelp-block-description": "Блокировка участника.",
"apihelp-block-param-user": "Имя участника, IP-адрес или диапазон IP-адресов, которые вы хотите заблокировать.",
"apihelp-block-param-reason": "Причина блокировки.",
"api-help-param-type-boolean": "Тип: двоичный ([[Special:ApiHelp/main#main/datatypes|details]])",
"api-help-param-type-timestamp": "Тип: {{PLURAL:$1|1=timestamp|2=list of timestamps}} ([[Special:ApiHelp/main#main/datatypes|allowed formats]])",
"api-help-param-type-user": "Тип: {{PLURAL:$1|1=user name|2=list of user names}}",
- "api-help-param-list": "{{PLURAL:$1|1=Одно из следующих значений|2=Значения (разделённые <kbd>{{!}}</kbd>)}}: $2",
+ "api-help-param-list": "{{PLURAL:$1|1=Одно из следующих значений|2=Значения (разделённые с помощью <kbd>{{!}}</kbd> или [[Special:ApiHelp/main#main/datatypes|альтернативного варианта]])}}: $2",
"api-help-param-list-can-be-empty": "{{PLURAL:$1|0=Должен быть пустым|может быть пустым, или $2}}",
"api-help-param-limit": "Не более чем $1 разрешено.",
"api-help-param-limit2": "Разрешено не более чем $1 ($2 для ботов).",
"api-help-param-integer-min": "{{PLURAL:$1|1=value|2=values}} должен быть не меньше чем $2.",
"api-help-param-integer-max": "{{PLURAL:$1|1=value|2=values}} должен быть не больше чем $3.",
"api-help-param-integer-minmax": "{{PLURAL:$1|1=value|2=values}} должен быть между $2 и $3.",
- "api-help-param-multi-separate": "Разделяйте значения с помощью <kbd>|</kbd>.",
+ "api-help-param-multi-separate": "Разделяйте значения с помощью <kbd>|</kbd> или [[Special:ApiHelp/main#main/datatypes|альтернативного варианта]].",
"api-help-param-multi-max": "Максимальное количество значений должно быть {{PLURAL:$1|$1}} ({{PLURAL:$2|$2}} для ботов).",
"api-help-param-default": "По умолчанию: $1",
"api-help-param-default-empty": "По умолчанию: <span class=\"apihelp-empty\">(пусто)</span>",
"apihelp-query+search-paramvalue-prop-sectionsnippet": "Adds a parsed snippet of the matching section title.",
"apihelp-query+search-paramvalue-prop-sectiontitle": "Adds the title of the matching section.",
"apihelp-query+search-paramvalue-prop-categorysnippet": "Adds a parsed snippet of the matching category.",
- "apihelp-query+search-paramvalue-prop-isfilematch": "Adds a boolean indicating if the search matched file content.",
+ "apihelp-query+search-paramvalue-prop-isfilematch": "添加布尔值,表明搜索是否匹配文件内容。",
"apihelp-query+search-paramvalue-prop-score": "<span class=\"apihelp-deprecated\">已弃用并已忽略。</span>",
"apihelp-query+search-paramvalue-prop-hasrelated": "<span class=\"apihelp-deprecated\">已弃用并已忽略。</span>",
"apihelp-query+search-param-limit": "返回的总计页面数。",
"api-help-permissions-granted-to": "{{PLURAL:$1|授予}}:$2",
"api-help-right-apihighlimits": "在API查询中使用更高的上限(慢查询:$1;快查询:$2)。慢查询的限制也适用于多值参数。",
"api-help-open-in-apisandbox": "<small>[在沙盒中打开]</small>",
- "api-help-authmanager-general-usage": "使用此模块的一般程序是:\n# 通过<kbd>amirequestsfor=$4</kbd>取得来自<kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd>的可用字段,和来自<kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>的<kbd>$5</kbd>令牌。\n# 向用户显示字段,并获得其提交内容。\n# 发送至此模块,提供<var>$1returnurl</var>及任何相关字段。\n# Check the <samp>status</samp> in the response.\n#* If you received <samp>PASS</samp> or <samp>FAIL</samp>, you're done. The operation either succeeded or it didn't.\n#* If you received <samp>UI</samp>, present the new fields to the user and obtain their submission. Then post to this module with <var>$1continue</var> and the relevant fields set, and repeat step 4.\n#* If you received <samp>REDIRECT</samp>, direct the user to the <samp>redirecttarget</samp> and wait for the return to <var>$1returnurl</var>. Then post to this module with <var>$1continue</var> and any fields passed to the return URL, and repeat step 4.\n#* If you received <samp>RESTART</samp>, that means the authentication worked but we don't have a linked user account. You might treat this as <samp>UI</samp> or as <samp>FAIL</samp>.",
+ "api-help-authmanager-general-usage": "使用此模块的一般程序是:\n# 通过<kbd>amirequestsfor=$4</kbd>取得来自<kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd>的可用字段,和来自<kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>的<kbd>$5</kbd>令牌。\n# 向用户显示字段,并获得其提交内容。\n# 发送至此模块,提供<var>$1returnurl</var>及任何相关字段。\n# 在响应中检查<samp>status</samp>。\n#* 如果您收到了<samp>PASS</samp>或<samp>FAIL</samp>,您已经完成。The operation either succeeded or it didn't.\n#* 如果您收到了<samp>UI</samp>,present the new fields to the user and obtain their submission. Then post to this module with <var>$1continue</var> and the relevant fields set, and repeat step 4.\n#* 如果您收到了<samp>REDIRECT</samp>,direct the user to the <samp>redirecttarget</samp> and wait for the return to <var>$1returnurl</var>. Then post to this module with <var>$1continue</var> and any fields passed to the return URL, and repeat step 4.\n#* 如果您收到了<samp>RESTART</samp>,that means the authentication worked but we don't have a linked user account. You might treat this as <samp>UI</samp> or as <samp>FAIL</samp>.",
"api-help-authmanagerhelper-request": "使用此身份验证请求,通过返回自<kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd>的<samp>id</samp>与<kbd>amirequestsfor=$1</kbd>。",
"api-help-authmanagerhelper-messageformat": "返回消息使用的格式。",
"api-help-authmanagerhelper-mergerequestfields": "合并用于所有身份验证请求的字段信息至一个数组中。",
+ "api-help-authmanagerhelper-preservestate": "从之前失败的登录尝试中保持状态,如果可能。",
+ "api-help-authmanagerhelper-continue": "此请求是在早先的<samp>UI</samp>或<samp>REDIRECT</samp>响应之后的附加请求。必需此值或<var>$1returnurl</var>。",
"api-help-authmanagerhelper-additional-params": "此模块允许额外参数,取决于可用的身份验证请求。使用<kbd>[[Special:ApiHelp/query+authmanagerinfo|action=query&meta=authmanagerinfo]]</kbd>与<kbd>amirequestsfor=$1</kbd>(或之前来自此模块的相应,如果可以)以决定可用请求及其使用的字段。",
"api-credits-header": "制作人员",
"api-credits": "API 开发人员:\n* Yuri Astrakhan(创建者,2006年9月~2007年9月的开发组领导)\n* Roan Kattouw(2007年9月~2009年的开发组领导)\n* Victor Vasiliev\n* Bryan Tong Minh\n* Sam Reed\n* Brad Jorsch(2013年至今的开发组领导)\n\n请将您的评论、建议和问题发送至mediawiki-api@lists.wikimedia.org,或提交错误请求至https://phabricator.wikimedia.org/。"
$user = User::newFromName( $res->username, 'usable' );
if ( !$user ) {
+ $provider = $this->getAuthenticationProvider( $state['primary'] );
throw new \DomainException(
get_class( $provider ) . " returned an invalid username: {$res->username}"
);
$this->logger->info( 'Login for {user} succeeded', [
'user' => $user->getName(),
] );
+ /** @var RememberMeAuthenticationRequest $req */
$req = AuthenticationRequest::getRequestByClass(
$beginReqs, RememberMeAuthenticationRequest::class
);
'creator' => $creator->getName(),
] );
$status = $user->addToDatabase();
- if ( !$status->isOk() ) {
+ if ( !$status->isOK() ) {
// @codeCoverageIgnoreStart
$ret = AuthenticationResponse::newFail( $status->getMessage() );
$this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
);
$logEntry->setPerformer( $isAnon ? $user : $creator );
$logEntry->setTarget( $user->getUserPage() );
+ /** @var CreationReasonAuthenticationRequest $req */
$req = AuthenticationRequest::getRequestByClass(
$state['reqs'], CreationReasonAuthenticationRequest::class
);
$this->logger->debug( __METHOD__ . ': name "{username}" is not creatable', [
'username' => $username,
] );
- $session->set( 'AuthManager::AutoCreateBlacklist', 'noname', 600 );
+ $session->set( 'AuthManager::AutoCreateBlacklist', 'noname' );
$user->setId( 0 );
$user->loadFromId();
return Status::newFatal( 'noname' );
'username' => $username,
'ip' => $anon->getName(),
] );
- $session->set( 'AuthManager::AutoCreateBlacklist', 'authmanager-autocreate-noperm', 600 );
+ $session->set( 'AuthManager::AutoCreateBlacklist', 'authmanager-autocreate-noperm' );
$session->persist();
$user->setId( 0 );
$user->loadFromId();
'username' => $username,
'reason' => $ret->getWikiText( null, null, 'en' ),
] );
- $session->set( 'AuthManager::AutoCreateBlacklist', $status, 600 );
+ $session->set( 'AuthManager::AutoCreateBlacklist', $status );
$user->setId( 0 );
$user->loadFromId();
return $ret;
$trxProfiler->setSilenced( true );
try {
$status = $user->addToDatabase();
- if ( !$status->isOk() ) {
+ if ( !$status->isOK() ) {
// Double-check for a race condition (T70012). We make use of the fact that when
// addToDatabase fails due to the user already existing, the user object gets loaded.
if ( $user->getId() ) {
*/
protected $caller;
- function __construct( $arr = [] ) {
+ /**
+ * LinkBatch constructor.
+ * @param LinkTarget[] $arr Initial items to be added to the batch
+ */
+ public function __construct( $arr = [] ) {
foreach ( $arr as $item ) {
$this->addObj( $item );
}
$title = $this->getTitle();
// Never send an RC notification email about categorization changes
- if ( $this->mAttribs['rc_type'] != RC_CATEGORIZE ) {
- if ( Hooks::run( 'AbortEmailNotification', [ $editor, $title, $this ] ) ) {
- # @todo FIXME: This would be better as an extension hook
+ if (
+ $this->mAttribs['rc_type'] != RC_CATEGORIZE &&
+ Hooks::run( 'AbortEmailNotification', [ $editor, $title, $this ] )
+ ) {
+ // @FIXME: This would be better as an extension hook
+ // Send emails or email jobs once this row is safely committed
+ $dbw->onTransactionIdle( function () use ( $editor, $title ) {
$enotif = new EmailNotification();
$enotif->notifyOnPageChange(
$editor,
$this->mAttribs['rc_last_oldid'],
$this->mExtra['pageStatus']
);
- }
+ } );
}
}
*/
const MAX_DELETE_USES = 5000;
+ /**
+ * @var string[]
+ */
+ private static $coreTags = [ 'mw-contentmodelchange' ];
+
/**
* Creates HTML for the given tags
*
// to be removed, a tag must not be defined by an extension, or equivalently it
// has to be either explicitly defined or not defined at all
// (assuming no edge case of a tag both explicitly-defined and extension-defined)
- $extensionDefinedTags = self::listExtensionDefinedTags();
- $intersect = array_intersect( $tagsToRemove, $extensionDefinedTags );
+ $softwareDefinedTags = self::listSoftwareDefinedTags();
+ $intersect = array_intersect( $tagsToRemove, $softwareDefinedTags );
if ( $intersect ) {
return self::restrictedTagError( 'tags-update-remove-not-allowed-one',
'tags-update-remove-not-allowed-multi', $intersect );
return Status::newFatal( 'tags-delete-too-many-uses', $tag, self::MAX_DELETE_USES );
}
- $extensionDefined = self::listExtensionDefinedTags();
- if ( in_array( $tag, $extensionDefined ) ) {
+ $softwareDefined = self::listSoftwareDefinedTags();
+ if ( in_array( $tag, $softwareDefined ) ) {
// extension-defined tags can't be deleted unless the extension
// specifically allows it
$status = Status::newFatal( 'tags-delete-not-allowed' );
}
/**
- * Lists those tags which extensions report as being "active".
+ * Lists those tags which core or extensions report as being "active".
*
* @return array
* @since 1.25
*/
- public static function listExtensionActivatedTags() {
+ public static function listSoftwareActivatedTags() {
+ // core active tags
+ $tags = self::$coreTags;
+ if ( !Hooks::isRegistered( 'ChangeTagsListActive' ) ) {
+ return $tags;
+ }
return ObjectCache::getMainWANInstance()->getWithSetCallback(
wfMemcKey( 'active-tags' ),
WANObjectCache::TTL_MINUTE * 5,
- function ( $oldValue, &$ttl, array &$setOpts ) {
+ function ( $oldValue, &$ttl, array &$setOpts ) use ( $tags ) {
$setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
// Ask extensions which tags they consider active
- $extensionActive = [];
- Hooks::run( 'ChangeTagsListActive', [ &$extensionActive ] );
- return $extensionActive;
+ Hooks::run( 'ChangeTagsListActive', [ &$tags ] );
+ return $tags;
},
[
'checkKeys' => [ wfMemcKey( 'active-tags' ) ],
);
}
+ /**
+ * @see listSoftwareActivatedTags
+ * @deprecated since 1.28 call listSoftwareActivatedTags directly
+ * @return array
+ */
+ public static function listExtensionActivatedTags() {
+ wfDeprecated( __METHOD__, '1.28' );
+ return self::listSoftwareActivatedTags();
+ }
+
/**
* Basically lists defined tags which count even if they aren't applied to anything.
* It returns a union of the results of listExplicitlyDefinedTags() and
*/
public static function listDefinedTags() {
$tags1 = self::listExplicitlyDefinedTags();
- $tags2 = self::listExtensionDefinedTags();
+ $tags2 = self::listSoftwareDefinedTags();
return array_values( array_unique( array_merge( $tags1, $tags2 ) ) );
}
}
/**
- * Lists tags defined by extensions using the ListDefinedTags hook.
+ * Lists tags defined by core or extensions using the ListDefinedTags hook.
* Extensions need only define those tags they deem to be in active use.
*
* Tries memcached first.
* @return string[] Array of strings: tags
* @since 1.25
*/
- public static function listExtensionDefinedTags() {
+ public static function listSoftwareDefinedTags() {
+ // core defined tags
+ $tags = self::$coreTags;
+ if ( !Hooks::isRegistered( 'ListDefinedTags' ) ) {
+ return $tags;
+ }
return ObjectCache::getMainWANInstance()->getWithSetCallback(
wfMemcKey( 'valid-tags-hook' ),
WANObjectCache::TTL_MINUTE * 5,
- function ( $oldValue, &$ttl, array &$setOpts ) {
+ function ( $oldValue, &$ttl, array &$setOpts ) use ( $tags ) {
$setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
- $tags = [];
Hooks::run( 'ListDefinedTags', [ &$tags ] );
return array_filter( array_unique( $tags ) );
},
);
}
+ /**
+ * Call listSoftwareDefinedTags directly
+ *
+ * @see listSoftwareDefinedTags
+ * @deprecated since 1.28
+ */
+ public static function listExtensionDefinedTags() {
+ wfDeprecated( __METHOD__, '1.28' );
+ return self::listSoftwareDefinedTags();
+ }
+
/**
* Invalidates the short-term cache of defined tags used by the
* list*DefinedTags functions, as well as the tag statistics cache.
protected function getContentClass() {
return JsonContent::class;
}
+
+ public function makeEmptyContent() {
+ $class = $this->getContentClass();
+ return new $class( '{}' );
+ }
}
+++ /dev/null
-<?php
-/**
- * Generator of database load balancing objects.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Database
- */
-
-/**
- * Class for ensuring a consistent ordering of events as seen by the user, despite replication.
- * Kind of like Hawking's [[Chronology Protection Agency]].
- */
-class ChronologyProtector {
- /** @var BagOStuff */
- protected $store;
-
- /** @var string Storage key name */
- protected $key;
- /** @var array Map of (ip: <IP>, agent: <user-agent>) */
- protected $client;
- /** @var bool Whether to no-op all method calls */
- protected $enabled = true;
- /** @var bool Whether to check and wait on positions */
- protected $wait = true;
-
- /** @var bool Whether the client data was loaded */
- protected $initialized = false;
- /** @var DBMasterPos[] Map of (DB master name => position) */
- protected $startupPositions = [];
- /** @var DBMasterPos[] Map of (DB master name => position) */
- protected $shutdownPositions = [];
-
- /**
- * @param BagOStuff $store
- * @param array $client Map of (ip: <IP>, agent: <user-agent>)
- * @since 1.27
- */
- public function __construct( BagOStuff $store, array $client ) {
- $this->store = $store;
- $this->client = $client;
- $this->key = $store->makeGlobalKey(
- 'ChronologyProtector',
- md5( $client['ip'] . "\n" . $client['agent'] )
- );
- }
-
- /**
- * @param bool $enabled Whether to no-op all method calls
- * @since 1.27
- */
- public function setEnabled( $enabled ) {
- $this->enabled = $enabled;
- }
-
- /**
- * @param bool $enabled Whether to check and wait on positions
- * @since 1.27
- */
- public function setWaitEnabled( $enabled ) {
- $this->wait = $enabled;
- }
-
- /**
- * Initialise a LoadBalancer to give it appropriate chronology protection.
- *
- * If the stash has a previous master position recorded, this will try to
- * make sure that the next query to a replica DB of that master will see changes up
- * to that position by delaying execution. The delay may timeout and allow stale
- * data if no non-lagged replica DBs are available.
- *
- * @param LoadBalancer $lb
- * @return void
- */
- public function initLB( LoadBalancer $lb ) {
- if ( !$this->enabled || $lb->getServerCount() <= 1 ) {
- return; // non-replicated setup or disabled
- }
-
- $this->initPositions();
-
- $masterName = $lb->getServerName( $lb->getWriterIndex() );
- if ( !empty( $this->startupPositions[$masterName] ) ) {
- $info = $lb->parentInfo();
- $pos = $this->startupPositions[$masterName];
- wfDebugLog( 'replication', __METHOD__ .
- ": LB '" . $info['id'] . "' waiting for master pos $pos\n" );
- $lb->waitFor( $pos );
- }
- }
-
- /**
- * Notify the ChronologyProtector that the LoadBalancer is about to shut
- * down. Saves replication positions.
- *
- * @param LoadBalancer $lb
- * @return void
- */
- public function shutdownLB( LoadBalancer $lb ) {
- if ( !$this->enabled || $lb->getServerCount() <= 1 ) {
- return; // non-replicated setup or disabled
- }
-
- $info = $lb->parentInfo();
- $masterName = $lb->getServerName( $lb->getWriterIndex() );
-
- // Only save the position if writes have been done on the connection
- $db = $lb->getAnyOpenConnection( $lb->getWriterIndex() );
- if ( !$db || !$db->doneWrites() ) {
- wfDebugLog( 'replication', __METHOD__ . ": LB {$info['id']}, no writes done\n" );
-
- return; // nothing to do
- }
-
- $pos = $db->getMasterPos();
- wfDebugLog( 'replication', __METHOD__ . ": LB {$info['id']} has master pos $pos\n" );
- $this->shutdownPositions[$masterName] = $pos;
- }
-
- /**
- * Notify the ChronologyProtector that the LBFactory is done calling shutdownLB() for now.
- * May commit chronology data to persistent storage.
- *
- * @return array Empty on success; returns the (db name => position) map on failure
- */
- public function shutdown() {
- if ( !$this->enabled || !count( $this->shutdownPositions ) ) {
- return true; // nothing to save
- }
-
- wfDebugLog( 'replication',
- __METHOD__ . ": saving master pos for " .
- implode( ', ', array_keys( $this->shutdownPositions ) ) . "\n"
- );
-
- // CP-protected writes should overwhemingly go to the master datacenter, so get DC-local
- // lock to merge the values. Use a DC-local get() and a synchronous all-DC set(). This
- // makes it possible for the BagOStuff class to write in parallel to all DCs with one RTT.
- if ( $this->store->lock( $this->key, 3 ) ) {
- $ok = $this->store->set(
- $this->key,
- self::mergePositions( $this->store->get( $this->key ), $this->shutdownPositions ),
- BagOStuff::TTL_MINUTE,
- BagOStuff::WRITE_SYNC
- );
- $this->store->unlock( $this->key );
- } else {
- $ok = false;
- }
-
- if ( !$ok ) {
- // Raced out too many times or stash is down
- wfDebugLog( 'replication',
- __METHOD__ . ": failed to save master pos for " .
- implode( ', ', array_keys( $this->shutdownPositions ) ) . "\n"
- );
-
- return $this->shutdownPositions;
- }
-
- return [];
- }
-
- /**
- * Load in previous master positions for the client
- */
- protected function initPositions() {
- if ( $this->initialized ) {
- return;
- }
-
- $this->initialized = true;
- if ( $this->wait ) {
- $data = $this->store->get( $this->key );
- $this->startupPositions = $data ? $data['positions'] : [];
-
- wfDebugLog( 'replication', __METHOD__ . ": key is {$this->key} (read)\n" );
- } else {
- $this->startupPositions = [];
-
- wfDebugLog( 'replication', __METHOD__ . ": key is {$this->key} (unread)\n" );
- }
- }
-
- /**
- * @param array|bool $curValue
- * @param DBMasterPos[] $shutdownPositions
- * @return array
- */
- private static function mergePositions( $curValue, array $shutdownPositions ) {
- /** @var $curPositions DBMasterPos[] */
- if ( $curValue === false ) {
- $curPositions = $shutdownPositions;
- } else {
- $curPositions = $curValue['positions'];
- // Use the newest positions for each DB master
- foreach ( $shutdownPositions as $db => $pos ) {
- if ( !isset( $curPositions[$db] )
- || $pos->asOfTime() > $curPositions[$db]->asOfTime()
- ) {
- $curPositions[$db] = $pos;
- }
- }
- }
-
- return [ 'positions' => $curPositions ];
- }
-}
/**
* Constructor
*
- * @param DatabaseBase $db A database subclass
+ * @param IDatabase $db A database subclass
* @param array $tablesToClone An array of tables to clone, unprefixed
* @param string $newTablePrefix Prefix to assign to the tables
* @param string $oldTablePrefix Prefix on current tables, if not $wgDBprefix
* @param bool $dropCurrentTables
*/
- public function __construct( DatabaseBase $db, array $tablesToClone,
+ public function __construct( IDatabase $db, array $tablesToClone,
$newTablePrefix, $oldTablePrefix = '', $dropCurrentTables = true
) {
$this->db = $db;
if ( $wgSharedDB && in_array( $tbl, $wgSharedTables, true ) ) {
// Shared tables don't work properly when cloning due to
// how prefixes are handled (bug 65654)
- throw new MWException( "Cannot clone shared table $tbl." );
+ throw new RuntimeException( "Cannot clone shared table $tbl." );
}
# Clean up from previous aborted run. So that table escaping
# works correctly across DB engines, we need to change the pre-
) {
if ( $oldTableName === $newTableName ) {
// Last ditch check to avoid data loss
- throw new MWException( "Not dropping new table, as '$newTableName'"
+ throw new LogicException( "Not dropping new table, as '$newTableName'"
. " is name of both the old and the new table." );
}
$this->db->dropTable( $tbl, __METHOD__ );
*/
public static function changePrefix( $prefix ) {
global $wgDBprefix;
- wfGetLBFactory()->forEachLB( function( $lb ) use ( $prefix ) {
- $lb->forEachOpenConnection( function ( $db ) use ( $prefix ) {
+ wfGetLBFactory()->forEachLB( function( LoadBalancer $lb ) use ( $prefix ) {
+ $lb->setDomainPrefix( $prefix );
+ $lb->forEachOpenConnection( function ( IDatabase $db ) use ( $prefix ) {
$db->tablePrefix( $prefix );
} );
} );
+++ /dev/null
-<?php
-/**
- * Helper class to handle automatically marking connections as reusable (via RAII pattern)
- * as well handling deferring the actual network connection until the handle is used
- *
- * @note: proxy methods are defined explicity to avoid interface errors
- * @ingroup Database
- * @since 1.22
- */
-class DBConnRef implements IDatabase {
- /** @var LoadBalancer */
- private $lb;
-
- /** @var DatabaseBase|null */
- private $conn;
-
- /** @var array|null */
- private $params;
-
- /**
- * @param LoadBalancer $lb
- * @param DatabaseBase|array $conn Connection or (server index, group, wiki ID) array
- */
- public function __construct( LoadBalancer $lb, $conn ) {
- $this->lb = $lb;
- if ( $conn instanceof DatabaseBase ) {
- $this->conn = $conn;
- } else {
- $this->params = $conn;
- }
- }
-
- function __call( $name, array $arguments ) {
- if ( $this->conn === null ) {
- list( $db, $groups, $wiki ) = $this->params;
- $this->conn = $this->lb->getConnection( $db, $groups, $wiki );
- }
-
- return call_user_func_array( [ $this->conn, $name ], $arguments );
- }
-
- public function getServerInfo() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function bufferResults( $buffer = null ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function trxLevel() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function trxTimestamp() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function explicitTrxActive() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function tablePrefix( $prefix = null ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function dbSchema( $schema = null ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getLBInfo( $name = null ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function setLBInfo( $name, $value = null ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function implicitGroupby() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function implicitOrderby() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function lastQuery() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function doneWrites() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function lastDoneWrites() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function writesPending() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function writesOrCallbacksPending() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function pendingWriteCallers() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function isOpen() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function restoreFlags( $state = self::RESTORE_PRIOR ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getFlag( $flag ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getProperty( $name ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getWikiID() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getType() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function open( $server, $user, $password, $dbName ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function fetchObject( $res ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function fetchRow( $res ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function numRows( $res ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function numFields( $res ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function fieldName( $res, $n ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function insertId() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function dataSeek( $res, $row ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function lastErrno() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function lastError() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function fieldInfo( $table, $field ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function affectedRows() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getSoftwareLink() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getServerVersion() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function close() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function reportConnectionError( $error = 'Unknown error' ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function freeResult( $res ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function selectField(
- $table, $var, $cond = '', $fname = __METHOD__, $options = []
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function selectFieldValues(
- $table, $var, $cond = '', $fname = __METHOD__, $options = []
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function select(
- $table, $vars, $conds = '', $fname = __METHOD__,
- $options = [], $join_conds = []
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function selectSQLText(
- $table, $vars, $conds = '', $fname = __METHOD__,
- $options = [], $join_conds = []
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function selectRow(
- $table, $vars, $conds, $fname = __METHOD__,
- $options = [], $join_conds = []
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function estimateRowCount(
- $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = []
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function selectRowCount(
- $tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function fieldExists( $table, $field, $fname = __METHOD__ ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function indexExists( $table, $index, $fname = __METHOD__ ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function tableExists( $table, $fname = __METHOD__ ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function indexUnique( $table, $index ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function makeList( $a, $mode = LIST_COMMA ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function bitNot( $field ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function bitAnd( $fieldLeft, $fieldRight ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function bitOr( $fieldLeft, $fieldRight ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function buildConcat( $stringList ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function buildGroupConcatField(
- $delim, $table, $field, $conds = '', $join_conds = []
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function selectDB( $db ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getDBname() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getServer() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function addQuotes( $s ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function buildLike() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function anyChar() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function anyString() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function nextSequenceValue( $seqName ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function upsert(
- $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function deleteJoin(
- $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function delete( $table, $conds, $fname = __METHOD__ ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function insertSelect(
- $destTable, $srcTable, $varMap, $conds,
- $fname = __METHOD__, $insertOptions = [], $selectOptions = []
- ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function unionSupportsOrderAndLimit() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function unionQueries( $sqls, $all ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function conditional( $cond, $trueVal, $falseVal ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function strreplace( $orig, $old, $new ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getServerUptime() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function wasDeadlock() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function wasLockTimeout() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function wasErrorReissuable() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function wasReadOnlyError() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function masterPosWait( DBMasterPos $pos, $timeout ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getSlavePos() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getMasterPos() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function serverIsReadOnly() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function onTransactionResolution( callable $callback ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function onTransactionIdle( callable $callback ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function onTransactionPreCommitOrIdle( callable $callback ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function setTransactionListener( $name, callable $callback = null ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function startAtomic( $fname = __METHOD__ ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function endAtomic( $fname = __METHOD__ ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function doAtomicSection( $fname, callable $callback ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function begin( $fname = __METHOD__, $mode = IDatabase::TRANSACTION_EXPLICIT ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function commit( $fname = __METHOD__, $flush = '' ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function rollback( $fname = __METHOD__, $flush = '' ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function flushSnapshot( $fname = __METHOD__ ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function listTables( $prefix = null, $fname = __METHOD__ ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function timestamp( $ts = 0 ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function timestampOrNull( $ts = null ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function ping( &$rtt = null ) {
- return func_num_args()
- ? $this->__call( __FUNCTION__, [ &$rtt ] )
- : $this->__call( __FUNCTION__, [] ); // method cares about null vs missing
- }
-
- public function getLag() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getSessionLagStatus() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function maxListLen() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function encodeBlob( $b ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function decodeBlob( $b ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function setSessionOptions( array $options ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function setSchemaVars( $vars ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function lockIsFree( $lockName, $method ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function lock( $lockName, $method, $timeout = 5 ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function unlock( $lockName, $method ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function namedLocksEnqueue() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function getInfinity() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function encodeExpiry( $expiry ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function decodeExpiry( $expiry, $format = TS_MW ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function setBigSelects( $value = true ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- public function isReadOnly() {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
- /**
- * Clean up the connection when out of scope
- */
- function __destruct() {
- if ( $this->conn !== null ) {
- $this->lb->reuseConnection( $this->conn );
- }
- }
-}
<?php
-
/**
* @defgroup Database Database
*
* @file
* @ingroup Database
*/
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
/**
* Database abstraction object
* @ingroup Database
*/
-abstract class DatabaseBase implements IDatabase {
+abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
/** Number of times to re-try an operation in case of deadlock */
const DEADLOCK_TRIES = 4;
/** Minimum time to wait before retry, in microseconds */
/** @var BagOStuff APC cache */
protected $srvCache;
+ /** @var LoggerInterface */
+ protected $connLogger;
+ /** @var LoggerInterface */
+ protected $queryLogger;
+ /** @var callback Error logging callback */
+ protected $errorLogger;
/** @var resource Database connection */
protected $mConn = null;
/** @var TransactionProfiler */
protected $trxProfiler;
+ /**
+ * Constructor.
+ *
+ * FIXME: It is possible to construct a Database object with no associated
+ * connection object, by specifying no parameters to __construct(). This
+ * feature is deprecated and should be removed.
+ *
+ * IDatabase classes should not be constructed directly in external
+ * code. DatabaseBase::factory() should be used instead.
+ *
+ * @param array $params Parameters passed from DatabaseBase::factory()
+ */
+ function __construct( array $params ) {
+ global $wgDBprefix, $wgDBmwschema;
+
+ $this->srvCache = ObjectCache::getLocalServerInstance( 'hash' );
+
+ $server = $params['host'];
+ $user = $params['user'];
+ $password = $params['password'];
+ $dbName = $params['dbname'];
+ $flags = $params['flags'];
+ $tablePrefix = $params['tablePrefix'];
+ $schema = $params['schema'];
+ $foreign = $params['foreign'];
+
+ $this->cliMode = isset( $params['cliMode'] )
+ ? $params['cliMode']
+ : ( PHP_SAPI === 'cli' );
+
+ $this->mFlags = $flags;
+ if ( $this->mFlags & DBO_DEFAULT ) {
+ if ( $this->cliMode ) {
+ $this->mFlags &= ~DBO_TRX;
+ } else {
+ $this->mFlags |= DBO_TRX;
+ }
+ }
+
+ $this->mSessionVars = $params['variables'];
+
+ /** Get the default table prefix*/
+ if ( $tablePrefix === 'get from global' ) {
+ $this->mTablePrefix = $wgDBprefix;
+ } else {
+ $this->mTablePrefix = $tablePrefix;
+ }
+
+ /** Get the database schema*/
+ if ( $schema === 'get from global' ) {
+ $this->mSchema = $wgDBmwschema;
+ } else {
+ $this->mSchema = $schema;
+ }
+
+ $this->mForeign = $foreign;
+
+ $this->profiler = isset( $params['profiler'] )
+ ? $params['profiler']
+ : Profiler::instance(); // @TODO: remove global state
+ $this->trxProfiler = isset( $params['trxProfiler'] )
+ ? $params['trxProfiler']
+ : new TransactionProfiler();
+ $this->connLogger = isset( $params['connLogger'] )
+ ? $params['connLogger']
+ : new \Psr\Log\NullLogger();
+ $this->queryLogger = isset( $params['queryLogger'] )
+ ? $params['queryLogger']
+ : new \Psr\Log\NullLogger();
+
+ if ( $user ) {
+ $this->open( $server, $user, $password, $dbName );
+ }
+ }
+
+ /**
+ * Given a DB type, construct the name of the appropriate child class of
+ * IDatabase. This is designed to replace all of the manual stuff like:
+ * $class = 'Database' . ucfirst( strtolower( $dbType ) );
+ * as well as validate against the canonical list of DB types we have
+ *
+ * This factory function is mostly useful for when you need to connect to a
+ * database other than the MediaWiki default (such as for external auth,
+ * an extension, et cetera). Do not use this to connect to the MediaWiki
+ * database. Example uses in core:
+ * @see LoadBalancer::reallyOpenConnection()
+ * @see ForeignDBRepo::getMasterDB()
+ * @see WebInstallerDBConnect::execute()
+ *
+ * @since 1.18
+ *
+ * @param string $dbType A possible DB type
+ * @param array $p An array of options to pass to the constructor.
+ * Valid options are: host, user, password, dbname, flags, tablePrefix, schema, driver
+ * @return IDatabase|null If the database driver or extension cannot be found
+ * @throws InvalidArgumentException If the database driver or extension cannot be found
+ */
+ final public static function factory( $dbType, $p = [] ) {
+ global $wgCommandLineMode;
+
+ $canonicalDBTypes = [
+ 'mysql' => [ 'mysqli', 'mysql' ],
+ 'postgres' => [],
+ 'sqlite' => [],
+ 'oracle' => [],
+ 'mssql' => [],
+ ];
+
+ $driver = false;
+ $dbType = strtolower( $dbType );
+ if ( isset( $canonicalDBTypes[$dbType] ) && $canonicalDBTypes[$dbType] ) {
+ $possibleDrivers = $canonicalDBTypes[$dbType];
+ if ( !empty( $p['driver'] ) ) {
+ if ( in_array( $p['driver'], $possibleDrivers ) ) {
+ $driver = $p['driver'];
+ } else {
+ throw new InvalidArgumentException( __METHOD__ .
+ " type '$dbType' does not support driver '{$p['driver']}'" );
+ }
+ } else {
+ foreach ( $possibleDrivers as $posDriver ) {
+ if ( extension_loaded( $posDriver ) ) {
+ $driver = $posDriver;
+ break;
+ }
+ }
+ }
+ } else {
+ $driver = $dbType;
+ }
+ if ( $driver === false ) {
+ throw new InvalidArgumentException( __METHOD__ .
+ " no viable database extension found for type '$dbType'" );
+ }
+
+ // Determine schema defaults. Currently Microsoft SQL Server uses $wgDBmwschema,
+ // and everything else doesn't use a schema (e.g. null)
+ // Although postgres and oracle support schemas, we don't use them (yet)
+ // to maintain backwards compatibility
+ $defaultSchemas = [
+ 'mssql' => 'get from global',
+ ];
+
+ $class = 'Database' . ucfirst( $driver );
+ if ( class_exists( $class ) && is_subclass_of( $class, 'IDatabase' ) ) {
+ // Resolve some defaults for b/c
+ $p['host'] = isset( $p['host'] ) ? $p['host'] : false;
+ $p['user'] = isset( $p['user'] ) ? $p['user'] : false;
+ $p['password'] = isset( $p['password'] ) ? $p['password'] : false;
+ $p['dbname'] = isset( $p['dbname'] ) ? $p['dbname'] : false;
+ $p['flags'] = isset( $p['flags'] ) ? $p['flags'] : 0;
+ $p['variables'] = isset( $p['variables'] ) ? $p['variables'] : [];
+ $p['tablePrefix'] = isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global';
+ if ( !isset( $p['schema'] ) ) {
+ $p['schema'] = isset( $defaultSchemas[$dbType] ) ? $defaultSchemas[$dbType] : null;
+ }
+ $p['foreign'] = isset( $p['foreign'] ) ? $p['foreign'] : false;
+ $p['cliMode'] = $wgCommandLineMode;
+
+ $conn = new $class( $p );
+ if ( isset( $p['connLogger'] ) ) {
+ $conn->connLogger = $p['connLogger'];
+ }
+ if ( isset( $p['queryLogger'] ) ) {
+ $conn->queryLogger = $p['queryLogger'];
+ }
+ if ( isset( $p['errorLogger'] ) ) {
+ $conn->errorLogger = $p['errorLogger'];
+ } else {
+ $conn->errorLogger = [ MWExceptionHandler::class, 'logException' ];
+ }
+ } else {
+ $conn = null;
+ }
+
+ return $conn;
+ }
+
+ public function setLogger( LoggerInterface $logger ) {
+ $this->queryLogger = $logger;
+ }
+
public function getServerInfo() {
return $this->getServerVersion();
}
}
}
- /**
- * Set a lazy-connecting DB handle to the master DB (for replication status purposes)
- *
- * @param IDatabase $conn
- * @since 1.27
- */
public function setLazyMasterHandle( IDatabase $conn ) {
$this->lazyMasterHandle = $conn;
}
return $this->lazyMasterHandle;
}
- /**
- * @return TransactionProfiler
- */
- protected function getTransactionProfiler() {
- return $this->trxProfiler;
- }
-
/**
* @param TransactionProfiler $profiler
* @since 1.27
*/
abstract function strencode( $s );
- /**
- * Constructor.
- *
- * FIXME: It is possible to construct a Database object with no associated
- * connection object, by specifying no parameters to __construct(). This
- * feature is deprecated and should be removed.
- *
- * DatabaseBase subclasses should not be constructed directly in external
- * code. DatabaseBase::factory() should be used instead.
- *
- * @param array $params Parameters passed from DatabaseBase::factory()
- */
- function __construct( array $params ) {
- global $wgDBprefix, $wgDBmwschema;
-
- $this->srvCache = ObjectCache::getLocalServerInstance( 'hash' );
-
- $server = $params['host'];
- $user = $params['user'];
- $password = $params['password'];
- $dbName = $params['dbname'];
- $flags = $params['flags'];
- $tablePrefix = $params['tablePrefix'];
- $schema = $params['schema'];
- $foreign = $params['foreign'];
-
- $this->cliMode = isset( $params['cliMode'] )
- ? $params['cliMode']
- : ( PHP_SAPI === 'cli' );
-
- $this->mFlags = $flags;
- if ( $this->mFlags & DBO_DEFAULT ) {
- if ( $this->cliMode ) {
- $this->mFlags &= ~DBO_TRX;
- } else {
- $this->mFlags |= DBO_TRX;
- }
- }
-
- $this->mSessionVars = $params['variables'];
-
- /** Get the default table prefix*/
- if ( $tablePrefix === 'get from global' ) {
- $this->mTablePrefix = $wgDBprefix;
- } else {
- $this->mTablePrefix = $tablePrefix;
- }
-
- /** Get the database schema*/
- if ( $schema === 'get from global' ) {
- $this->mSchema = $wgDBmwschema;
- } else {
- $this->mSchema = $schema;
- }
-
- $this->mForeign = $foreign;
-
- $this->profiler = isset( $params['profiler'] )
- ? $params['profiler']
- : Profiler::instance(); // @TODO: remove global state
- $this->trxProfiler = isset( $params['trxProfiler'] )
- ? $params['trxProfiler']
- : new TransactionProfiler();
-
- if ( $user ) {
- $this->open( $server, $user, $password, $dbName );
- }
-
- }
-
/**
* Called by serialize. Throw an exception when DB connection is serialized.
* This causes problems on some database engines because the connection is
* not restored on unserialize.
*/
public function __sleep() {
- throw new MWException( 'Database serialization may cause problems, since ' .
+ throw new RuntimeException( 'Database serialization may cause problems, since ' .
'the connection is not restored on wakeup.' );
}
- /**
- * Given a DB type, construct the name of the appropriate child class of
- * DatabaseBase. This is designed to replace all of the manual stuff like:
- * $class = 'Database' . ucfirst( strtolower( $dbType ) );
- * as well as validate against the canonical list of DB types we have
- *
- * This factory function is mostly useful for when you need to connect to a
- * database other than the MediaWiki default (such as for external auth,
- * an extension, et cetera). Do not use this to connect to the MediaWiki
- * database. Example uses in core:
- * @see LoadBalancer::reallyOpenConnection()
- * @see ForeignDBRepo::getMasterDB()
- * @see WebInstallerDBConnect::execute()
- *
- * @since 1.18
- *
- * @param string $dbType A possible DB type
- * @param array $p An array of options to pass to the constructor.
- * Valid options are: host, user, password, dbname, flags, tablePrefix, schema, driver
- * @throws MWException If the database driver or extension cannot be found
- * @return DatabaseBase|null DatabaseBase subclass or null
- */
- final public static function factory( $dbType, $p = [] ) {
- global $wgCommandLineMode;
-
- $canonicalDBTypes = [
- 'mysql' => [ 'mysqli', 'mysql' ],
- 'postgres' => [],
- 'sqlite' => [],
- 'oracle' => [],
- 'mssql' => [],
- ];
-
- $driver = false;
- $dbType = strtolower( $dbType );
- if ( isset( $canonicalDBTypes[$dbType] ) && $canonicalDBTypes[$dbType] ) {
- $possibleDrivers = $canonicalDBTypes[$dbType];
- if ( !empty( $p['driver'] ) ) {
- if ( in_array( $p['driver'], $possibleDrivers ) ) {
- $driver = $p['driver'];
- } else {
- throw new MWException( __METHOD__ .
- " cannot construct Database with type '$dbType' and driver '{$p['driver']}'" );
- }
- } else {
- foreach ( $possibleDrivers as $posDriver ) {
- if ( extension_loaded( $posDriver ) ) {
- $driver = $posDriver;
- break;
- }
- }
- }
- } else {
- $driver = $dbType;
- }
- if ( $driver === false ) {
- throw new MWException( __METHOD__ .
- " no viable database extension found for type '$dbType'" );
- }
-
- // Determine schema defaults. Currently Microsoft SQL Server uses $wgDBmwschema,
- // and everything else doesn't use a schema (e.g. null)
- // Although postgres and oracle support schemas, we don't use them (yet)
- // to maintain backwards compatibility
- $defaultSchemas = [
- 'mssql' => 'get from global',
- ];
-
- $class = 'Database' . ucfirst( $driver );
- if ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) {
- // Resolve some defaults for b/c
- $p['host'] = isset( $p['host'] ) ? $p['host'] : false;
- $p['user'] = isset( $p['user'] ) ? $p['user'] : false;
- $p['password'] = isset( $p['password'] ) ? $p['password'] : false;
- $p['dbname'] = isset( $p['dbname'] ) ? $p['dbname'] : false;
- $p['flags'] = isset( $p['flags'] ) ? $p['flags'] : 0;
- $p['variables'] = isset( $p['variables'] ) ? $p['variables'] : [];
- $p['tablePrefix'] = isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global';
- if ( !isset( $p['schema'] ) ) {
- $p['schema'] = isset( $defaultSchemas[$dbType] ) ? $defaultSchemas[$dbType] : null;
- }
- $p['foreign'] = isset( $p['foreign'] ) ? $p['foreign'] : false;
- $p['cliMode'] = $wgCommandLineMode;
-
- return new $class( $p );
- } else {
- return null;
- }
- }
-
protected function installErrorHandler() {
$this->mPHPError = false;
$this->htmlErrors = ini_set( 'html_errors', '0' );
- set_error_handler( [ $this, 'connectionErrorHandler' ] );
+ set_error_handler( [ $this, 'connectionerrorLogger' ] );
}
/**
* @param int $errno
* @param string $errstr
*/
- public function connectionErrorHandler( $errno, $errstr ) {
+ public function connectionerrorLogger( $errno, $errstr ) {
$this->mPHPError = $errstr;
}
/**
- * Create a log context to pass to wfLogDBError or other logging functions.
+ * Create a log context to pass to PSR logging functions.
*
* @param array $extras Additional data to add to context
* @return array
public function close() {
if ( $this->mConn ) {
if ( $this->trxLevel() ) {
- if ( !$this->mTrxAutomatic ) {
- wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " .
- " performing implicit commit before closing connection!" );
- }
-
$this->commit( __METHOD__, self::FLUSHING_INTERNAL );
}
$closed = $this->closeConnection();
$this->mConn = false;
} elseif ( $this->mTrxIdleCallbacks || $this->mTrxEndCallbacks ) { // sanity
- throw new MWException( "Transaction callbacks still pending." );
+ throw new RuntimeException( "Transaction callbacks still pending." );
} else {
$closed = true;
}
# Keep track of whether the transaction has write queries pending
if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $isWrite ) {
$this->mTrxDoneWrites = true;
- $this->getTransactionProfiler()->transactionWritingIn(
+ $this->trxProfiler->transactionWritingIn(
$this->mServer, $this->mDBname, $this->mTrxShortId );
}
if ( $this->debug() ) {
- wfDebugLog( 'queries', sprintf( "%s: %s", $this->mDBname, $commentedSql ) );
+ $this->queryLogger->debug( "{$this->mDBname} {$commentedSql}" );
}
# Avoid fatals if close() was called
$lastErrno = $this->lastErrno();
# Update state tracking to reflect transaction loss due to disconnection
$this->handleTransactionLoss();
- wfDebug( "Connection lost, reconnecting...\n" );
if ( $this->reconnect() ) {
- wfDebug( "Reconnected\n" );
$msg = __METHOD__ . ": lost connection to {$this->getServer()}; reconnected";
- wfDebugLog( 'DBPerformance', "$msg:\n" . wfBacktrace( true ) );
+ $this->connLogger->warning( $msg );
+ $this->queryLogger->warning(
+ "$msg:\n" . ( new RuntimeException() )->getTraceAsString() );
if ( !$recoverable ) {
# Callers may catch the exception and continue to use the DB
$ret = $this->doProfiledQuery( $sql, $commentedSql, $isWrite, $fname );
}
} else {
- wfDebug( "Failed\n" );
+ $msg = __METHOD__ . ": lost connection to {$this->getServer()} permanently";
+ $this->connLogger->error( $msg );
}
}
# generalizeSQL() will probably cut down the query to reasonable
# logging size most of the time. The substr is really just a sanity check.
if ( $isMaster ) {
- $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
+ $queryProf = 'query-m: ' . substr( self::generalizeSQL( $sql ), 0, 255 );
} else {
- $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
+ $queryProf = 'query: ' . substr( self::generalizeSQL( $sql ), 0, 255 );
}
# Include query transaction state
$this->mRTTEstimate = $queryRuntime;
}
- $this->getTransactionProfiler()->recordQueryCompletion(
+ $this->trxProfiler->recordQueryCompletion(
$queryProf, $startTime, $isWrite, $this->affectedRows()
);
MWDebug::query( $sql, $fname, $isMaster, $queryRuntime );
public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
if ( $this->ignoreErrors() || $tempIgnore ) {
- wfDebug( "SQL ERROR (ignored): $error\n" );
+ $this->queryLogger->debug( "SQL ERROR (ignored): $error\n" );
} else {
$sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 );
- wfLogDBError(
+ $this->queryLogger->error(
"{fname}\t{db_server}\t{errno}\t{error}\t{sql1line}",
$this->getLogContext( [
'method' => __METHOD__,
'fname' => $fname,
] )
);
- wfDebug( "SQL ERROR: " . $error . "\n" );
+ $this->queryLogger->debug( "SQL ERROR: " . $error . "\n" );
throw new DBQueryError( $this, $error, $errno, $sql, $fname );
}
}
*
* @return array
*/
- protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) {
+ protected function prepare( $sql, $func = __METHOD__ ) {
/* MySQL doesn't support prepared statements (yet), so just
* pack up the query for reference. We'll manually replace
* the bits later.
} else {
$useIndex = '';
}
+ if ( isset( $options['IGNORE INDEX'] ) && is_string( $options['IGNORE INDEX'] ) ) {
+ $ignoreIndex = $this->ignoreIndexClause( $options['IGNORE INDEX'] );
+ } else {
+ $ignoreIndex = '';
+ }
- return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail ];
+ return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
}
/**
$useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
? $options['USE INDEX']
: [];
+ $ignoreIndexes = ( isset( $options['IGNORE INDEX'] ) && is_array( $options['IGNORE INDEX'] ) )
+ ? $options['IGNORE INDEX']
+ : [];
if ( is_array( $table ) ) {
$from = ' FROM ' .
- $this->tableNamesWithUseIndexOrJOIN( $table, $useIndexes, $join_conds );
+ $this->tableNamesWithIndexClauseOrJOIN( $table, $useIndexes, $ignoreIndexes, $join_conds );
} elseif ( $table != '' ) {
if ( $table[0] == ' ' ) {
$from = ' FROM ' . $table;
} else {
$from = ' FROM ' .
- $this->tableNamesWithUseIndexOrJOIN( [ $table ], $useIndexes, [] );
+ $this->tableNamesWithIndexClauseOrJOIN( [ $table ], $useIndexes, $ignoreIndexes, [] );
}
} else {
$from = '';
}
- list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) =
+ list( $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ) =
$this->makeSelectOptions( $options );
if ( !empty( $conds ) ) {
if ( is_array( $conds ) ) {
$conds = $this->makeList( $conds, LIST_AND );
}
- $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail";
+ $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex WHERE $conds $preLimitTail";
} else {
- $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail";
+ $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex $preLimitTail";
}
if ( isset( $options['LIMIT'] ) ) {
public function makeList( $a, $mode = LIST_COMMA ) {
if ( !is_array( $a ) ) {
- throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' );
+ throw new DBUnexpectedError( $this, __METHOD__ . ' called with incorrect parameters' );
}
$first = true;
unset( $value[$nullKey] );
}
if ( count( $value ) == 0 && !$includeNull ) {
- throw new MWException( __METHOD__ . ": empty input for field $field" );
+ throw new InvalidArgumentException( __METHOD__ . ": empty input for field $field" );
} elseif ( count( $value ) == 0 ) {
// only check if $field is null
$list .= "$field IS NULL";
/**
* Get the aliased table name clause for a FROM clause
- * which might have a JOIN and/or USE INDEX clause
+ * which might have a JOIN and/or USE INDEX or IGNORE INDEX clause
*
* @param array $tables ( [alias] => table )
* @param array $use_index Same as for select()
+ * @param array $ignore_index Same as for select()
* @param array $join_conds Same as for select()
* @return string
*/
- protected function tableNamesWithUseIndexOrJOIN(
- $tables, $use_index = [], $join_conds = []
+ protected function tableNamesWithIndexClauseOrJOIN(
+ $tables, $use_index = [], $ignore_index = [], $join_conds = []
) {
$ret = [];
$retJOIN = [];
$use_index = (array)$use_index;
+ $ignore_index = (array)$ignore_index;
$join_conds = (array)$join_conds;
foreach ( $tables as $alias => $table ) {
$tableClause .= ' ' . $use;
}
}
+ if ( isset( $ignore_index[$alias] ) ) { // has IGNORE INDEX?
+ $ignore = $this->ignoreIndexClause( implode( ',', (array)$ignore_index[$alias] ) );
+ if ( $ignore != '' ) {
+ $tableClause .= ' ' . $ignore;
+ }
+ }
$on = $this->makeList( (array)$conds, LIST_AND );
if ( $on != '' ) {
$tableClause .= ' ON (' . $on . ')';
implode( ',', (array)$use_index[$alias] )
);
+ $ret[] = $tableClause;
+ } elseif ( isset( $ignore_index[$alias] ) ) {
+ // Is there an INDEX clause for this table?
+ $tableClause = $this->tableNameWithAlias( $table, $alias );
+ $tableClause .= ' ' . $this->ignoreIndexClause(
+ implode( ',', (array)$ignore_index[$alias] )
+ );
+
$ret[] = $tableClause;
} else {
$tableClause = $this->tableNameWithAlias( $table, $alias );
return '';
}
+ /**
+ * IGNORE INDEX clause. Unlikely to be useful for anything but MySQL. This
+ * is only needed because a) MySQL must be as efficient as possible due to
+ * its use on Wikipedia, and b) MySQL 4.0 is kind of dumb sometimes about
+ * which index to pick. Anyway, other databases might have different
+ * indexes on a given table. So don't bother overriding this unless you're
+ * MySQL.
+ * @param string $index
+ * @return string
+ */
+ public function ignoreIndexClause( $index ) {
+ return '';
+ }
+
public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
$quotedTable = $this->tableName( $table );
$fname = __METHOD__
) {
if ( !$conds ) {
- throw new DBUnexpectedError( $this,
- 'DatabaseBase::deleteJoin() called with empty $conds' );
+ throw new DBUnexpectedError( $this, __METHOD__ . ' called with empty $conds' );
}
$delTable = $this->tableName( $delTable );
public function textFieldSize( $table, $field ) {
$table = $this->tableName( $table );
$sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
- $res = $this->query( $sql, 'DatabaseBase::textFieldSize' );
+ $res = $this->query( $sql, __METHOD__ );
$row = $this->fetchObject( $res );
$m = [];
public function delete( $table, $conds, $fname = __METHOD__ ) {
if ( !$conds ) {
- throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' );
+ throw new DBUnexpectedError( $this, __METHOD__ . ' called with no conditions' );
}
$table = $this->tableName( $table );
$selectOptions = [ $selectOptions ];
}
- list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
+ list( $startOpts, $useIndex, $tailOpts, $ignoreIndex ) = $this->makeSelectOptions(
+ $selectOptions );
if ( is_array( $srcTable ) ) {
$srcTable = implode( ',', array_map( [ &$this, 'tableName' ], $srcTable ) );
$sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
" SELECT $startOpts " . implode( ',', $varMap ) .
- " FROM $srcTable $useIndex ";
+ " FROM $srcTable $useIndex $ignoreIndex ";
if ( $conds != '*' ) {
if ( is_array( $conds ) ) {
$this->clearFlag( DBO_TRX ); // restore auto-commit
}
} catch ( Exception $ex ) {
- MWExceptionHandler::logException( $ex );
+ call_user_func( $this->errorLogger, $ex );
$e = $e ?: $ex;
// Some callbacks may use startAtomic/endAtomic, so make sure
// their transactions are ended so other callbacks don't fail
list( $phpCallback ) = $callback;
call_user_func( $phpCallback );
} catch ( Exception $ex ) {
- MWExceptionHandler::logException( $ex );
+ call_user_func( $this->errorLogger, $ex );
$e = $e ?: $ex;
}
}
list( $phpCallback ) = $callback;
$phpCallback( $trigger, $this );
} catch ( Exception $ex ) {
- MWExceptionHandler::logException( $ex );
+ call_user_func( $this->errorLogger, $ex );
$e = $e ?: $ex;
}
}
} else {
// @TODO: make this an exception at some point
$msg = "$fname: Implicit transaction already active (from {$this->mTrxFname}).";
- wfLogDBError( $msg );
- wfWarn( $msg );
+ $this->queryLogger->error( $msg );
return; // join the main transaction set
}
} elseif ( $this->getFlag( DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) {
// @TODO: make this an exception at some point
$msg = "$fname: Implicit transaction expected (DBO_TRX set).";
- wfLogDBError( $msg );
- wfWarn( $msg );
+ $this->queryLogger->error( $msg );
return; // let any writes be in the main transaction
}
}
} else {
if ( !$this->mTrxLevel ) {
- wfWarn( "$fname: No transaction to commit, something got out of sync." );
+ $this->queryLogger->error( "$fname: No transaction to commit, something got out of sync." );
return; // nothing to do
} elseif ( $this->mTrxAutomatic ) {
// @TODO: make this an exception at some point
$msg = "$fname: Explicit commit of implicit transaction.";
- wfLogDBError( $msg );
- wfWarn( $msg );
+ $this->queryLogger->error( $msg );
return; // wait for the main transaction set commit round
}
}
$this->assertOpen();
$this->runOnTransactionPreCommitCallbacks();
- $writeTime = $this->pendingWriteQueryDuration();
+ $writeTime = $this->pendingWriteQueryDuration( self::ESTIMATE_DB_APPLY );
$this->doCommit( $fname );
if ( $this->mTrxDoneWrites ) {
$this->mDoneWrites = microtime( true );
- $this->getTransactionProfiler()->transactionWritingOut(
+ $this->trxProfiler->transactionWritingOut(
$this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime );
}
}
} else {
if ( !$this->mTrxLevel ) {
- wfWarn( "$fname: No transaction to rollback, something got out of sync." );
+ $this->queryLogger->error(
+ "$fname: No transaction to rollback, something got out of sync." );
return; // nothing to do
} elseif ( $this->getFlag( DBO_TRX ) ) {
throw new DBUnexpectedError(
$this->doRollback( $fname );
$this->mTrxAtomicLevels = [];
if ( $this->mTrxDoneWrites ) {
- $this->getTransactionProfiler()->transactionWritingOut(
+ $this->trxProfiler->transactionWritingOut(
$this->mServer, $this->mDBname, $this->mTrxShortId );
}
* @param string $newName Name of table to be created
* @param bool $temporary Whether the new table should be temporary
* @param string $fname Calling function name
- * @throws MWException
+ * @throws RuntimeException
* @return bool True if operation was successful
*/
public function duplicateTableStructure( $oldName, $newName, $temporary = false,
$fname = __METHOD__
) {
- throw new MWException(
- 'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
+ throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
}
function listTables( $prefix = null, $fname = __METHOD__ ) {
- throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
+ throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
}
/**
*
* @param string $prefix Only show VIEWs with this prefix, eg. unit_test_
* @param string $fname Name of calling function
- * @throws MWException
+ * @throws RuntimeException
* @return array
* @since 1.22
*/
public function listViews( $prefix = null, $fname = __METHOD__ ) {
- throw new MWException( 'DatabaseBase::listViews is not implemented in descendant class' );
+ throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
}
/**
* Differentiates between a TABLE and a VIEW
*
* @param string $name Name of the database-structure to test.
- * @throws MWException
+ * @throws RuntimeException
* @return bool
* @since 1.22
*/
public function isView( $name ) {
- throw new MWException( 'DatabaseBase::isView is not implemented in descendant class' );
+ throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
}
public function timestamp( $ts = 0 ) {
* generated dynamically using $filename
* @param bool|callable $inputCallback Optional function called for each
* complete line sent
- * @throws Exception|MWException
* @return bool|string
+ * @throws Exception
*/
public function sourceFile(
$filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false
MediaWiki\restoreWarnings();
if ( false === $fp ) {
- throw new MWException( "Could not open \"{$filename}\".\n" );
+ throw new RuntimeException( "Could not open \"{$filename}\".\n" );
}
if ( !$fname ) {
+++ /dev/null
-<?php
-/**
- * This file contains database error classes.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Database
- */
-
-/**
- * Database error base class
- * @ingroup Database
- */
-class DBError extends MWException {
- /** @var DatabaseBase */
- public $db;
-
- /**
- * Construct a database error
- * @param DatabaseBase $db Object which threw the error
- * @param string $error A simple error message to be used for debugging
- */
- function __construct( DatabaseBase $db = null, $error ) {
- $this->db = $db;
- parent::__construct( $error );
- }
-}
-
-/**
- * Base class for the more common types of database errors. These are known to occur
- * frequently, so we try to give friendly error messages for them.
- *
- * @ingroup Database
- * @since 1.23
- */
-class DBExpectedError extends DBError {
- /**
- * @return string
- */
- function getText() {
- global $wgShowDBErrorBacktrace;
-
- $s = $this->getTextContent() . "\n";
-
- if ( $wgShowDBErrorBacktrace ) {
- $s .= "Backtrace:\n" . $this->getTraceAsString() . "\n";
- }
-
- return $s;
- }
-
- /**
- * @return string
- */
- function getHTML() {
- global $wgShowDBErrorBacktrace;
-
- $s = $this->getHTMLContent();
-
- if ( $wgShowDBErrorBacktrace ) {
- $s .= '<p>Backtrace:</p><pre>' . htmlspecialchars( $this->getTraceAsString() ) . '</pre>';
- }
-
- return $s;
- }
-
- function getPageTitle() {
- return $this->msg( 'databaseerror', 'Database error' );
- }
-
- /**
- * @return string
- */
- protected function getTextContent() {
- return $this->getMessage();
- }
-
- /**
- * @return string
- */
- protected function getHTMLContent() {
- return '<p>' . nl2br( htmlspecialchars( $this->getTextContent() ) ) . '</p>';
- }
-}
-
-/**
- * @ingroup Database
- */
-class DBConnectionError extends DBExpectedError {
- /** @var string Error text */
- public $error;
-
- /**
- * @param DatabaseBase $db Object throwing the error
- * @param string $error Error text
- */
- function __construct( DatabaseBase $db = null, $error = 'unknown error' ) {
- $msg = 'DB connection error';
-
- if ( trim( $error ) != '' ) {
- $msg .= ": $error";
- } elseif ( $db ) {
- $error = $this->db->getServer();
- }
-
- parent::__construct( $db, $msg );
- $this->error = $error;
- }
-
- /**
- * @return bool
- */
- function useOutputPage() {
- // Not likely to work
- return false;
- }
-
- /**
- * @param string $key
- * @param string $fallback Unescaped alternative error text in case the
- * message cache cannot be used. Can contain parameters as in regular
- * messages, that should be passed as additional parameters.
- * @return string Unprocessed plain error text with parameters replaced
- */
- function msg( $key, $fallback /*[, params...] */ ) {
- $args = array_slice( func_get_args(), 2 );
-
- if ( $this->useMessageCache() ) {
- return wfMessage( $key, $args )->useDatabase( false )->text();
- } else {
- return wfMsgReplaceArgs( $fallback, $args );
- }
- }
-
- /**
- * @return bool
- */
- function isLoggable() {
- // Don't send to the exception log, already in dberror log
- return false;
- }
-
- /**
- * @return string Safe HTML
- */
- function getHTML() {
- global $wgShowDBErrorBacktrace, $wgShowHostnames, $wgShowSQLErrors;
-
- $sorry = htmlspecialchars( $this->msg(
- 'dberr-problems',
- 'Sorry! This site is experiencing technical difficulties.'
- ) );
- $again = htmlspecialchars( $this->msg(
- 'dberr-again',
- 'Try waiting a few minutes and reloading.'
- ) );
-
- if ( $wgShowHostnames || $wgShowSQLErrors ) {
- $info = str_replace(
- '$1', Html::element( 'span', [ 'dir' => 'ltr' ], $this->error ),
- htmlspecialchars( $this->msg( 'dberr-info', '(Cannot access the database: $1)' ) )
- );
- } else {
- $info = htmlspecialchars( $this->msg(
- 'dberr-info-hidden',
- '(Cannot access the database)'
- ) );
- }
-
- # No database access
- MessageCache::singleton()->disable();
-
- $html = "<h1>$sorry</h1><p>$again</p><p><small>$info</small></p>";
-
- if ( $wgShowDBErrorBacktrace ) {
- $html .= '<p>Backtrace:</p><pre>' . htmlspecialchars( $this->getTraceAsString() ) . '</pre>';
- }
-
- $html .= '<hr />';
- $html .= $this->searchForm();
-
- return $html;
- }
-
- protected function getTextContent() {
- global $wgShowHostnames, $wgShowSQLErrors;
-
- if ( $wgShowHostnames || $wgShowSQLErrors ) {
- return $this->getMessage();
- } else {
- return 'DB connection error';
- }
- }
-
- /**
- * Output the exception report using HTML.
- *
- * @return void
- */
- public function reportHTML() {
- global $wgUseFileCache;
-
- // Check whether we can serve a file-cached copy of the page with the error underneath
- if ( $wgUseFileCache ) {
- try {
- $cache = $this->fileCachedPage();
- // Cached version on file system?
- if ( $cache !== null ) {
- // Hack: extend the body for error messages
- $cache = str_replace( [ '</html>', '</body>' ], '', $cache );
- // Add cache notice...
- $cache .= '<div style="border:1px solid #ffd0d0;padding:1em;">' .
- htmlspecialchars( $this->msg( 'dberr-cachederror',
- 'This is a cached copy of the requested page, and may not be up to date.' ) ) .
- '</div>';
-
- // Output cached page with notices on bottom and re-close body
- echo "{$cache}<hr />{$this->getHTML()}</body></html>";
-
- return;
- }
- } catch ( Exception $e ) {
- // Do nothing, just use the default page
- }
- }
-
- // We can't, cough and die in the usual fashion
- parent::reportHTML();
- }
-
- /**
- * @return string
- */
- function searchForm() {
- global $wgSitename, $wgCanonicalServer, $wgRequest;
-
- $usegoogle = htmlspecialchars( $this->msg(
- 'dberr-usegoogle',
- 'You can try searching via Google in the meantime.'
- ) );
- $outofdate = htmlspecialchars( $this->msg(
- 'dberr-outofdate',
- 'Note that their indexes of our content may be out of date.'
- ) );
- $googlesearch = htmlspecialchars( $this->msg( 'searchbutton', 'Search' ) );
-
- $search = htmlspecialchars( $wgRequest->getVal( 'search' ) );
-
- $server = htmlspecialchars( $wgCanonicalServer );
- $sitename = htmlspecialchars( $wgSitename );
-
- $trygoogle = <<<EOT
-<div style="margin: 1.5em">$usegoogle<br />
-<small>$outofdate</small>
-</div>
-<form method="get" action="//www.google.com/search" id="googlesearch">
- <input type="hidden" name="domains" value="$server" />
- <input type="hidden" name="num" value="50" />
- <input type="hidden" name="ie" value="UTF-8" />
- <input type="hidden" name="oe" value="UTF-8" />
-
- <input type="text" name="q" size="31" maxlength="255" value="$search" />
- <input type="submit" name="btnG" value="$googlesearch" />
- <p>
- <label><input type="radio" name="sitesearch" value="$server" checked="checked" />$sitename</label>
- <label><input type="radio" name="sitesearch" value="" />WWW</label>
- </p>
-</form>
-EOT;
-
- return $trygoogle;
- }
-
- /**
- * @return string
- */
- private function fileCachedPage() {
- $context = RequestContext::getMain();
-
- if ( $context->getOutput()->isDisabled() ) {
- // Done already?
- return '';
- }
-
- if ( $context->getTitle() ) {
- // Use the main context's title if we managed to set it
- $t = $context->getTitle()->getPrefixedDBkey();
- } else {
- // Fallback to the raw title URL param. We can't use the Title
- // class is it may hit the interwiki table and give a DB error.
- // We may get a cache miss due to not sanitizing the title though.
- $t = str_replace( ' ', '_', $context->getRequest()->getVal( 'title' ) );
- if ( $t == '' ) { // fallback to main page
- $t = Title::newFromText(
- $this->msg( 'mainpage', 'Main Page' ) )->getPrefixedDBkey();
- }
- }
-
- $cache = new HTMLFileCache( $t, 'view' );
- if ( $cache->isCached() ) {
- return $cache->fetchText();
- } else {
- return '';
- }
- }
-}
-
-/**
- * @ingroup Database
- */
-class DBQueryError extends DBExpectedError {
- public $error, $errno, $sql, $fname;
-
- /**
- * @param DatabaseBase $db
- * @param string $error
- * @param int|string $errno
- * @param string $sql
- * @param string $fname
- */
- function __construct( DatabaseBase $db, $error, $errno, $sql, $fname ) {
- if ( $db->wasConnectionError( $errno ) ) {
- $message = "A connection error occured. \n" .
- "Query: $sql\n" .
- "Function: $fname\n" .
- "Error: $errno $error\n";
- } else {
- $message = "A database error has occurred. Did you forget to run " .
- "maintenance/update.php after upgrading? See: " .
- "https://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" .
- "Query: $sql\n" .
- "Function: $fname\n" .
- "Error: $errno $error\n";
- }
- parent::__construct( $db, $message );
-
- $this->error = $error;
- $this->errno = $errno;
- $this->sql = $sql;
- $this->fname = $fname;
- }
-
- /**
- * @return string
- */
- function getPageTitle() {
- return $this->msg( 'databaseerror', 'Database error' );
- }
-
- /**
- * @return string
- */
- protected function getHTMLContent() {
- $key = 'databaseerror-text';
- $s = Html::element( 'p', [], $this->msg( $key, $this->getFallbackMessage( $key ) ) );
-
- $details = $this->getTechnicalDetails();
- if ( $details ) {
- $s .= '<ul>';
- foreach ( $details as $key => $detail ) {
- $s .= str_replace(
- '$1', call_user_func_array( 'Html::element', $detail ),
- Html::element( 'li', [],
- $this->msg( $key, $this->getFallbackMessage( $key ) )
- )
- );
- }
- $s .= '</ul>';
- }
-
- return $s;
- }
-
- /**
- * @return string
- */
- protected function getTextContent() {
- $key = 'databaseerror-textcl';
- $s = $this->msg( $key, $this->getFallbackMessage( $key ) ) . "\n";
-
- foreach ( $this->getTechnicalDetails() as $key => $detail ) {
- $s .= $this->msg( $key, $this->getFallbackMessage( $key ), $detail[2] ) . "\n";
- }
-
- return $s;
- }
-
- /**
- * Make a list of technical details that can be shown to the user. This information can
- * aid in debugging yet may be useful to an attacker trying to exploit a security weakness
- * in the software or server configuration.
- *
- * Thus no such details are shown by default, though if $wgShowHostnames is true, only the
- * full SQL query is hidden; in fact, the error message often does contain a hostname, and
- * sites using this option probably don't care much about "security by obscurity". Of course,
- * if $wgShowSQLErrors is true, the SQL query *is* shown.
- *
- * @return array Keys are message keys; values are arrays of arguments for Html::element().
- * Array will be empty if users are not allowed to see any of these details at all.
- */
- protected function getTechnicalDetails() {
- global $wgShowHostnames, $wgShowSQLErrors;
-
- $attribs = [ 'dir' => 'ltr' ];
- $details = [];
-
- if ( $wgShowSQLErrors ) {
- $details['databaseerror-query'] = [
- 'div', [ 'class' => 'mw-code' ] + $attribs, $this->sql ];
- }
-
- if ( $wgShowHostnames || $wgShowSQLErrors ) {
- $errorMessage = $this->errno . ' ' . $this->error;
- $details['databaseerror-function'] = [ 'code', $attribs, $this->fname ];
- $details['databaseerror-error'] = [ 'samp', $attribs, $errorMessage ];
- }
-
- return $details;
- }
-
- /**
- * @param string $key Message key
- * @return string English message text
- */
- private function getFallbackMessage( $key ) {
- $messages = [
- 'databaseerror-text' => 'A database query error has occurred.
-This may indicate a bug in the software.',
- 'databaseerror-textcl' => 'A database query error has occurred.',
- 'databaseerror-query' => 'Query: $1',
- 'databaseerror-function' => 'Function: $1',
- 'databaseerror-error' => 'Error: $1',
- ];
-
- return $messages[$key];
- }
-}
-
-/**
- * @ingroup Database
- */
-class DBUnexpectedError extends DBError {
-}
-
-/**
- * @ingroup Database
- */
-class DBReadOnlyError extends DBExpectedError {
- function getPageTitle() {
- return $this->msg( 'readonly', 'Database is locked' );
- }
-}
-
-/**
- * @ingroup Database
- */
-class DBTransactionError extends DBExpectedError {
-}
-
-/**
- * Exception class for attempted DB access
- * @ingroup Database
- */
-class DBAccessError extends DBUnexpectedError {
- public function __construct() {
- parent::__construct( "Mediawiki tried to access the database via wfGetDB(). " .
- "This is not allowed, because database access has been disabled." );
- }
-}
-
-/**
- * Exception class for replica DB wait timeouts
- * @ingroup Database
- */
-class DBReplicationWaitError extends DBUnexpectedError {
-}
* @param string $password
* @param string $dbName
* @throws DBConnectionError
- * @return bool|DatabaseBase|null
+ * @return bool|resource|null
*/
public function open( $server, $user, $password, $dbName ) {
# Test for driver support, to avoid suppressed fatal error
* @return bool
* @throws DBUnexpectedError
* @throws Exception
- * @throws MWException
*/
function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
$table = $this->tableName( $table );
* @param array $binaryColumns Contains a list of column names that are binary types
* This is a custom parameter only present for MS SQL.
*
- * @throws MWException|DBUnexpectedError
+ * @throws DBUnexpectedError
* @return string
*/
public function makeList( $a, $mode = LIST_COMMA, $binaryColumns = [] ) {
if ( !is_array( $a ) ) {
- throw new DBUnexpectedError( $this,
- 'DatabaseBase::makeList called with incorrect parameters' );
+ throw new DBUnexpectedError( $this, __METHOD__ . ' called with incorrect parameters' );
}
if ( $mode != LIST_NAMES ) {
* Throws an exception if it is invalid.
* Reference: http://msdn.microsoft.com/en-us/library/aa224033%28v=SQL.80%29.aspx
* @param string $identifier
- * @throws MWException
+ * @throws InvalidArgumentException
* @return string
*/
private function escapeIdentifier( $identifier ) {
if ( strlen( $identifier ) == 0 ) {
- throw new MWException( "An identifier must not be empty" );
+ throw new InvalidArgumentException( "An identifier must not be empty" );
}
if ( strlen( $identifier ) > 128 ) {
- throw new MWException( "The identifier '$identifier' is too long (max. 128)" );
+ throw new InvalidArgumentException( "The identifier '$identifier' is too long (max. 128)" );
}
if ( ( strpos( $identifier, '[' ) !== false )
|| ( strpos( $identifier, ']' ) !== false )
) {
// It may be allowed if you quoted with double quotation marks, but
// that would break if QUOTED_IDENTIFIER is OFF
- throw new MWException( "Square brackets are not allowed in '$identifier'" );
+ throw new InvalidArgumentException( "Square brackets are not allowed in '$identifier'" );
}
return "[$identifier]";
}
// we want this to be compatible with the output of parent::makeSelectOptions()
- return [ $startOpts, '', $tailOpts, '' ];
+ return [ $startOpts, '', $tailOpts, '', '' ];
}
/**
return wfSetVar( $this->mIgnoreErrors, $value );
}
} // end DatabaseMssql class
-
-/**
- * Utility class.
- *
- * @ingroup Database
- */
-class MssqlField implements Field {
- private $name, $tableName, $default, $max_length, $nullable, $type;
-
- function __construct( $info ) {
- $this->name = $info['COLUMN_NAME'];
- $this->tableName = $info['TABLE_NAME'];
- $this->default = $info['COLUMN_DEFAULT'];
- $this->max_length = $info['CHARACTER_MAXIMUM_LENGTH'];
- $this->nullable = !( strtolower( $info['IS_NULLABLE'] ) == 'no' );
- $this->type = $info['DATA_TYPE'];
- }
-
- function name() {
- return $this->name;
- }
-
- function tableName() {
- return $this->tableName;
- }
-
- function defaultValue() {
- return $this->default;
- }
-
- function maxLength() {
- return $this->max_length;
- }
-
- function isNullable() {
- return $this->nullable;
- }
-
- function type() {
- return $this->type;
- }
-}
-
-class MssqlBlob extends Blob {
- public function __construct( $data ) {
- if ( $data instanceof MssqlBlob ) {
- return $data;
- } elseif ( $data instanceof Blob ) {
- $this->mData = $data->fetch();
- } elseif ( is_array( $data ) && is_object( $data ) ) {
- $this->mData = serialize( $data );
- } else {
- $this->mData = $data;
- }
- }
-
- /**
- * Returns an unquoted hex representation of a binary string
- * for insertion into varbinary-type fields
- * @return string
- */
- public function fetch() {
- if ( $this->mData === null ) {
- return 'null';
- }
-
- $ret = '0x';
- $dataLength = strlen( $this->mData );
- for ( $i = 0; $i < $dataLength; $i++ ) {
- $ret .= bin2hex( pack( 'C', ord( $this->mData[$i] ) ) );
- }
-
- return $ret;
- }
-}
-
-class MssqlResultWrapper extends ResultWrapper {
- private $mSeekTo = null;
-
- /**
- * @return stdClass|bool
- */
- public function fetchObject() {
- $res = $this->result;
-
- if ( $this->mSeekTo !== null ) {
- $result = sqlsrv_fetch_object( $res, 'stdClass', [],
- SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
- $this->mSeekTo = null;
- } else {
- $result = sqlsrv_fetch_object( $res );
- }
-
- // MediaWiki expects us to return boolean false when there are no more rows instead of null
- if ( $result === null ) {
- return false;
- }
-
- return $result;
- }
-
- /**
- * @return array|bool
- */
- public function fetchRow() {
- $res = $this->result;
-
- if ( $this->mSeekTo !== null ) {
- $result = sqlsrv_fetch_array( $res, SQLSRV_FETCH_BOTH,
- SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
- $this->mSeekTo = null;
- } else {
- $result = sqlsrv_fetch_array( $res );
- }
-
- // MediaWiki expects us to return boolean false when there are no more rows instead of null
- if ( $result === null ) {
- return false;
- }
-
- return $result;
- }
-
- /**
- * @param int $row
- * @return bool
- */
- public function seek( $row ) {
- $res = $this->result;
-
- // check bounds
- $numRows = $this->db->numRows( $res );
- $row = intval( $row );
-
- if ( $numRows === 0 ) {
- return false;
- } elseif ( $row < 0 || $row > $numRows - 1 ) {
- return false;
- }
-
- // Unlike MySQL, the seek actually happens on the next access
- $this->mSeekTo = $row;
- return true;
- }
-}
return "FORCE INDEX (" . $this->indexName( $index ) . ")";
}
+ /**
+ * @param string $index
+ * @return string
+ */
+ function ignoreIndexClause( $index ) {
+ return "IGNORE INDEX (" . $this->indexName( $index ) . ")";
+ }
+
/**
* @return string
*/
*/
function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__ ) {
if ( !$conds ) {
- throw new DBUnexpectedError( $this, 'DatabaseBase::deleteJoin() called with empty $conds' );
+ throw new DBUnexpectedError( $this, __METHOD__ . ' called with empty $conds' );
}
$delTable = $this->tableName( $delTable );
}
}
-/**
- * Utility class.
- * @ingroup Database
- */
-class MySQLField implements Field {
- private $name, $tablename, $default, $max_length, $nullable,
- $is_pk, $is_unique, $is_multiple, $is_key, $type, $binary,
- $is_numeric, $is_blob, $is_unsigned, $is_zerofill;
-
- function __construct( $info ) {
- $this->name = $info->name;
- $this->tablename = $info->table;
- $this->default = $info->def;
- $this->max_length = $info->max_length;
- $this->nullable = !$info->not_null;
- $this->is_pk = $info->primary_key;
- $this->is_unique = $info->unique_key;
- $this->is_multiple = $info->multiple_key;
- $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
- $this->type = $info->type;
- $this->binary = isset( $info->binary ) ? $info->binary : false;
- $this->is_numeric = isset( $info->numeric ) ? $info->numeric : false;
- $this->is_blob = isset( $info->blob ) ? $info->blob : false;
- $this->is_unsigned = isset( $info->unsigned ) ? $info->unsigned : false;
- $this->is_zerofill = isset( $info->zerofill ) ? $info->zerofill : false;
- }
-
- /**
- * @return string
- */
- function name() {
- return $this->name;
- }
-
- /**
- * @return string
- */
- function tableName() {
- return $this->tablename;
- }
-
- /**
- * @return string
- */
- function type() {
- return $this->type;
- }
-
- /**
- * @return bool
- */
- function isNullable() {
- return $this->nullable;
- }
-
- function defaultValue() {
- return $this->default;
- }
-
- /**
- * @return bool
- */
- function isKey() {
- return $this->is_key;
- }
-
- /**
- * @return bool
- */
- function isMultipleKey() {
- return $this->is_multiple;
- }
-
- /**
- * @return bool
- */
- function isBinary() {
- return $this->binary;
- }
-
- /**
- * @return bool
- */
- function isNumeric() {
- return $this->is_numeric;
- }
-
- /**
- * @return bool
- */
- function isBlob() {
- return $this->is_blob;
- }
-
- /**
- * @return bool
- */
- function isUnsigned() {
- return $this->is_unsigned;
- }
-
- /**
- * @return bool
- */
- function isZerofill() {
- return $this->is_zerofill;
- }
-}
-
-/**
- * DBMasterPos class for MySQL/MariaDB
- *
- * Note that master positions and sync logic here make some assumptions:
- * - Binlog-based usage assumes single-source replication and non-hierarchical replication.
- * - GTID-based usage allows getting/syncing with multi-source replication. It is assumed
- * that GTID sets are complete (e.g. include all domains on the server).
- */
-class MySQLMasterPos implements DBMasterPos {
- /** @var string Binlog file */
- public $file;
- /** @var int Binglog file position */
- public $pos;
- /** @var string[] GTID list */
- public $gtids = [];
- /** @var float UNIX timestamp */
- public $asOfTime = 0.0;
-
- /**
- * @param string $file Binlog file name
- * @param integer $pos Binlog position
- * @param string $gtid Comma separated GTID set [optional]
- */
- function __construct( $file, $pos, $gtid = '' ) {
- $this->file = $file;
- $this->pos = $pos;
- $this->gtids = array_map( 'trim', explode( ',', $gtid ) );
- $this->asOfTime = microtime( true );
- }
-
- /**
- * @return string <binlog file>/<position>, e.g db1034-bin.000976/843431247
- */
- function __toString() {
- return "{$this->file}/{$this->pos}";
- }
-
- function asOfTime() {
- return $this->asOfTime;
- }
-
- function hasReached( DBMasterPos $pos ) {
- if ( !( $pos instanceof self ) ) {
- throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
- }
-
- // Prefer GTID comparisons, which work with multi-tier replication
- $thisPosByDomain = $this->getGtidCoordinates();
- $thatPosByDomain = $pos->getGtidCoordinates();
- if ( $thisPosByDomain && $thatPosByDomain ) {
- $reached = true;
- // Check that this has positions GTE all of those in $pos for all domains in $pos
- foreach ( $thatPosByDomain as $domain => $thatPos ) {
- $thisPos = isset( $thisPosByDomain[$domain] ) ? $thisPosByDomain[$domain] : -1;
- $reached = $reached && ( $thatPos <= $thisPos );
- }
-
- return $reached;
- }
-
- // Fallback to the binlog file comparisons
- $thisBinPos = $this->getBinlogCoordinates();
- $thatBinPos = $pos->getBinlogCoordinates();
- if ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] ) {
- return ( $thisBinPos['pos'] >= $thatBinPos['pos'] );
- }
-
- // Comparing totally different binlogs does not make sense
- return false;
- }
-
- function channelsMatch( DBMasterPos $pos ) {
- if ( !( $pos instanceof self ) ) {
- throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
- }
-
- // Prefer GTID comparisons, which work with multi-tier replication
- $thisPosDomains = array_keys( $this->getGtidCoordinates() );
- $thatPosDomains = array_keys( $pos->getGtidCoordinates() );
- if ( $thisPosDomains && $thatPosDomains ) {
- // Check that this has GTIDs for all domains in $pos
- return !array_diff( $thatPosDomains, $thisPosDomains );
- }
-
- // Fallback to the binlog file comparisons
- $thisBinPos = $this->getBinlogCoordinates();
- $thatBinPos = $pos->getBinlogCoordinates();
-
- return ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] );
- }
-
- /**
- * @note: this returns false for multi-source replication GTID sets
- * @see https://mariadb.com/kb/en/mariadb/gtid
- * @see https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-concepts.html
- * @return array Map of (domain => integer position) or false
- */
- protected function getGtidCoordinates() {
- $gtidInfos = [];
- foreach ( $this->gtids as $gtid ) {
- $m = [];
- // MariaDB style: <domain>-<server id>-<sequence number>
- if ( preg_match( '!^(\d+)-\d+-(\d+)$!', $gtid, $m ) ) {
- $gtidInfos[(int)$m[1]] = (int)$m[2];
- // MySQL style: <UUID domain>:<sequence number>
- } elseif ( preg_match( '!^(\w{8}-\w{4}-\w{4}-\w{4}-\w{12}):(\d+)$!', $gtid, $m ) ) {
- $gtidInfos[$m[1]] = (int)$m[2];
- } else {
- $gtidInfos = [];
- break; // unrecognized GTID
- }
-
- }
-
- return $gtidInfos;
- }
-
- /**
- * @see http://dev.mysql.com/doc/refman/5.7/en/show-master-status.html
- * @see http://dev.mysql.com/doc/refman/5.7/en/show-slave-status.html
- * @return array|bool (binlog, (integer file number, integer position)) or false
- */
- protected function getBinlogCoordinates() {
- $m = [];
- if ( preg_match( '!^(.+)\.(\d+)/(\d+)$!', (string)$this, $m ) ) {
- return [ 'binlog' => $m[1], 'pos' => [ (int)$m[2], (int)$m[3] ] ];
- }
-
- return false;
- }
-}
}
/**
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param resource $stmt A valid OCI statement identifier
* @param bool $unique
*/
}
}
-/**
- * Utility class.
- * @ingroup Database
- */
-class ORAField implements Field {
- private $name, $tablename, $default, $max_length, $nullable,
- $is_pk, $is_unique, $is_multiple, $is_key, $type;
-
- function __construct( $info ) {
- $this->name = $info['column_name'];
- $this->tablename = $info['table_name'];
- $this->default = $info['data_default'];
- $this->max_length = $info['data_length'];
- $this->nullable = $info['not_null'];
- $this->is_pk = isset( $info['prim'] ) && $info['prim'] == 1 ? 1 : 0;
- $this->is_unique = isset( $info['uniq'] ) && $info['uniq'] == 1 ? 1 : 0;
- $this->is_multiple = isset( $info['nonuniq'] ) && $info['nonuniq'] == 1 ? 1 : 0;
- $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
- $this->type = $info['data_type'];
- }
-
- function name() {
- return $this->name;
- }
-
- function tableName() {
- return $this->tablename;
- }
-
- function defaultValue() {
- return $this->default;
- }
-
- function maxLength() {
- return $this->max_length;
- }
-
- function isNullable() {
- return $this->nullable;
- }
-
- function isKey() {
- return $this->is_key;
- }
-
- function isMultipleKey() {
- return $this->is_multiple;
- }
-
- function type() {
- return $this->type;
- }
-}
-
/**
* @ingroup Database
*/
* @param string $password
* @param string $dbName
* @throws DBConnectionError
- * @return DatabaseBase|null
+ * @return resource|null
*/
function open( $server, $user, $password, $dbName ) {
global $wgDBOracleDRCP;
protected function doQuery( $sql ) {
wfDebug( "SQL: [$sql]\n" );
if ( !StringUtils::isUtf8( $sql ) ) {
- throw new MWException( "SQL encoding is invalid\n$sql" );
+ throw new InvalidArgumentException( "SQL encoding is invalid\n$sql" );
}
// handle some oracle specifics
if ( !is_array( $selectOptions ) ) {
$selectOptions = [ $selectOptions ];
}
- list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
+ list( $startOpts, $useIndex, $tailOpts, $ignoreIndex ) =
+ $this->makeSelectOptions( $selectOptions );
if ( is_array( $srcTable ) ) {
$srcTable = implode( ',', array_map( [ &$this, 'tableName' ], $srcTable ) );
} else {
$sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
" SELECT $startOpts " . implode( ',', $varMap ) .
- " FROM $srcTable $useIndex ";
+ " FROM $srcTable $useIndex $ignoreIndex ";
if ( $conds != '*' ) {
$sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
}
$useIndex = '';
}
- return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail ];
+ if ( isset( $options['IGNORE INDEX'] ) && !is_array( $options['IGNORE INDEX'] ) ) {
+ $ignoreIndex = $this->ignoreIndexClause( $options['IGNORE INDEX'] );
+ } else {
+ $ignoreIndex = '';
+ }
+
+ return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
}
public function delete( $table, $conds, $fname = __METHOD__ ) {
$has_default, $default;
/**
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param string $table
* @param string $field
* @return null|PostgresField
protected $didbegin;
/**
- * @param DatabaseBase $dbw
+ * @param IDatabase $dbw
* @param int $id
*/
public function __construct( $dbw, $id ) {
* @param string $password
* @param string $dbName
* @throws DBConnectionError|Exception
- * @return DatabaseBase|null
+ * @return resource|bool|null
*/
function open( $server, $user, $password, $dbName ) {
# Test for Postgres support, to avoid suppressed fatal error
if ( !is_array( $selectOptions ) ) {
$selectOptions = [ $selectOptions ];
}
- list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
+ list( $startOpts, $useIndex, $tailOpts, $ignoreIndex ) =
+ $this->makeSelectOptions( $selectOptions );
if ( is_array( $srcTable ) ) {
$srcTable = implode( ',', array_map( [ &$this, 'tableName' ], $srcTable ) );
} else {
$sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
" SELECT $startOpts " . implode( ',', $varMap ) .
- " FROM $srcTable $useIndex";
+ " FROM $srcTable $useIndex $ignoreIndex ";
if ( $conds != '*' ) {
$sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
*/
function makeSelectOptions( $options ) {
$preLimitTail = $postLimitTail = '';
- $startOpts = $useIndex = '';
+ $startOpts = $useIndex = $ignoreIndex = '';
$noKeyOptions = [];
foreach ( $options as $key => $option ) {
$startOpts .= 'DISTINCT';
}
- return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail ];
+ return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
}
function getDBname() {
return Wikimedia\base_convert( substr( sha1( $lockName ), 0, 15 ), 16, 10 );
}
} // end DatabasePostgres class
-
-class PostgresBlob extends Blob {
-}
return $this->query( "ATTACH DATABASE $file AS $name", $fname );
}
- /**
- * @see DatabaseBase::isWriteQuery()
- *
- * @param string $sql
- * @return bool
- */
function isWriteQuery( $sql ) {
return parent::isWriteQuery( $sql ) && !preg_match( '/^(ATTACH|PRAGMA)\b/i', $sql );
}
}
/**
- * @throws MWException
* @param string $oldName
* @param string $newName
* @param bool $temporary
* @param string $fname
* @return bool|ResultWrapper
+ * @throws RuntimeException
*/
function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
$res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name=" .
$this->addQuotes( $oldName ) . " AND type='table'", $fname );
$obj = $this->fetchObject( $res );
if ( !$obj ) {
- throw new MWException( "Couldn't retrieve structure for table $oldName" );
+ throw new RuntimeException( "Couldn't retrieve structure for table $oldName" );
}
$sql = $obj->sql;
$sql = preg_replace(
}
} // end DatabaseSqlite class
-
-/**
- * @ingroup Database
- */
-class SQLiteField implements Field {
- private $info, $tableName;
-
- function __construct( $info, $tableName ) {
- $this->info = $info;
- $this->tableName = $tableName;
- }
-
- function name() {
- return $this->info->name;
- }
-
- function tableName() {
- return $this->tableName;
- }
-
- function defaultValue() {
- if ( is_string( $this->info->dflt_value ) ) {
- // Typically quoted
- if ( preg_match( '/^\'(.*)\'$', $this->info->dflt_value ) ) {
- return str_replace( "''", "'", $this->info->dflt_value );
- }
- }
-
- return $this->info->dflt_value;
- }
-
- /**
- * @return bool
- */
- function isNullable() {
- return !$this->info->notnull;
- }
-
- function type() {
- return $this->info->type;
- }
-} // end SQLiteField
+++ /dev/null
-<?php
-/**
- * This file contains database-related utility classes.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Database
- */
-
-/**
- * Utility class
- * @ingroup Database
- *
- * This allows us to distinguish a blob from a normal string and an array of strings
- */
-class Blob {
- /** @var string */
- protected $mData;
-
- function __construct( $data ) {
- $this->mData = $data;
- }
-
- function fetch() {
- return $this->mData;
- }
-}
-
-/**
- * Base for all database-specific classes representing information about database fields
- * @ingroup Database
- */
-interface Field {
- /**
- * Field name
- * @return string
- */
- function name();
-
- /**
- * Name of table this field belongs to
- * @return string
- */
- function tableName();
-
- /**
- * Database type
- * @return string
- */
- function type();
-
- /**
- * Whether this field can store NULL values
- * @return bool
- */
- function isNullable();
-}
-
-/**
- * Result wrapper for grabbing data queried by someone else
- * @ingroup Database
- */
-class ResultWrapper implements Iterator {
- /** @var resource */
- public $result;
-
- /** @var DatabaseBase */
- protected $db;
-
- /** @var int */
- protected $pos = 0;
-
- /** @var object|null */
- protected $currentRow = null;
-
- /**
- * Create a new result object from a result resource and a Database object
- *
- * @param DatabaseBase $database
- * @param resource|ResultWrapper $result
- */
- function __construct( $database, $result ) {
- $this->db = $database;
-
- if ( $result instanceof ResultWrapper ) {
- $this->result = $result->result;
- } else {
- $this->result = $result;
- }
- }
-
- /**
- * Get the number of rows in a result object
- *
- * @return int
- */
- function numRows() {
- return $this->db->numRows( $this );
- }
-
- /**
- * Fetch the next row from the given result object, in object form. Fields can be retrieved with
- * $row->fieldname, with fields acting like member variables. If no more rows are available,
- * false is returned.
- *
- * @return stdClass|bool
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- function fetchObject() {
- return $this->db->fetchObject( $this );
- }
-
- /**
- * Fetch the next row from the given result object, in associative array form. Fields are
- * retrieved with $row['fieldname']. If no more rows are available, false is returned.
- *
- * @return array|bool
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- function fetchRow() {
- return $this->db->fetchRow( $this );
- }
-
- /**
- * Free a result object
- */
- function free() {
- $this->db->freeResult( $this );
- unset( $this->result );
- unset( $this->db );
- }
-
- /**
- * Change the position of the cursor in a result object.
- * See mysql_data_seek()
- *
- * @param int $row
- */
- function seek( $row ) {
- $this->db->dataSeek( $this, $row );
- }
-
- /*
- * ======= Iterator functions =======
- * Note that using these in combination with the non-iterator functions
- * above may cause rows to be skipped or repeated.
- */
-
- function rewind() {
- if ( $this->numRows() ) {
- $this->db->dataSeek( $this, 0 );
- }
- $this->pos = 0;
- $this->currentRow = null;
- }
-
- /**
- * @return stdClass|array|bool
- */
- function current() {
- if ( is_null( $this->currentRow ) ) {
- $this->next();
- }
-
- return $this->currentRow;
- }
-
- /**
- * @return int
- */
- function key() {
- return $this->pos;
- }
-
- /**
- * @return stdClass
- */
- function next() {
- $this->pos++;
- $this->currentRow = $this->fetchObject();
-
- return $this->currentRow;
- }
-
- /**
- * @return bool
- */
- function valid() {
- return $this->current() !== false;
- }
-}
-
-/**
- * Overloads the relevant methods of the real ResultsWrapper so it
- * doesn't go anywhere near an actual database.
- */
-class FakeResultWrapper extends ResultWrapper {
- /** @var array */
- public $result = [];
-
- /** @var null And it's going to stay that way :D */
- protected $db = null;
-
- /** @var int */
- protected $pos = 0;
-
- /** @var array|stdClass|bool */
- protected $currentRow = null;
-
- /**
- * @param array $array
- */
- function __construct( $array ) {
- $this->result = $array;
- }
-
- /**
- * @return int
- */
- function numRows() {
- return count( $this->result );
- }
-
- /**
- * @return array|bool
- */
- function fetchRow() {
- if ( $this->pos < count( $this->result ) ) {
- $this->currentRow = $this->result[$this->pos];
- } else {
- $this->currentRow = false;
- }
- $this->pos++;
- if ( is_object( $this->currentRow ) ) {
- return get_object_vars( $this->currentRow );
- } else {
- return $this->currentRow;
- }
- }
-
- function seek( $row ) {
- $this->pos = $row;
- }
-
- function free() {
- }
-
- /**
- * Callers want to be able to access fields with $this->fieldName
- * @return bool|stdClass
- */
- function fetchObject() {
- $this->fetchRow();
- if ( $this->currentRow ) {
- return (object)$this->currentRow;
- } else {
- return false;
- }
- }
-
- function rewind() {
- $this->pos = 0;
- $this->currentRow = null;
- }
-
- /**
- * @return bool|stdClass
- */
- function next() {
- return $this->fetchObject();
- }
-}
-
-/**
- * Used by DatabaseBase::buildLike() to represent characters that have special
- * meaning in SQL LIKE clauses and thus need no escaping. Don't instantiate it
- * manually, use DatabaseBase::anyChar() and anyString() instead.
- */
-class LikeMatch {
- /** @var string */
- private $str;
-
- /**
- * Store a string into a LikeMatch marker object.
- *
- * @param string $s
- */
- public function __construct( $s ) {
- $this->str = $s;
- }
-
- /**
- * Return the original stored string.
- *
- * @return string
- */
- public function toString() {
- return $this->str;
- }
-}
-
-/**
- * An object representing a master or replica DB position in a replicated setup.
- *
- * The implementation details of this opaque type are up to the database subclass.
- */
-interface DBMasterPos {
- /**
- * @return float UNIX timestamp
- * @since 1.25
- */
- public function asOfTime();
-
- /**
- * @param DBMasterPos $pos
- * @return bool Whether this position is at or higher than $pos
- * @since 1.27
- */
- public function hasReached( DBMasterPos $pos );
-
- /**
- * @param DBMasterPos $pos
- * @return bool Whether this position appears to be for the same channel as another
- * @since 1.27
- */
- public function channelsMatch( DBMasterPos $pos );
-
- /**
- * @return string
- * @since 1.27
- */
- public function __toString();
-}
+++ /dev/null
-<?php
-
-/**
- * @defgroup Database Database
- *
- * This file deals with database interface functions
- * and query specifics/optimisations.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Database
- */
-
-/**
- * Basic database interface for live and lazy-loaded DB handles
- *
- * @todo: loosen up DB classes from MWException
- * @note: IDatabase and DBConnRef should be updated to reflect any changes
- * @ingroup Database
- */
-interface IDatabase {
- /** @var int Callback triggered immediately due to no active transaction */
- const TRIGGER_IDLE = 1;
- /** @var int Callback triggered by COMMIT */
- const TRIGGER_COMMIT = 2;
- /** @var int Callback triggered by ROLLBACK */
- const TRIGGER_ROLLBACK = 3;
-
- /** @var string Transaction is requested by regular caller outside of the DB layer */
- const TRANSACTION_EXPLICIT = '';
- /** @var string Transaction is requested internally via DBO_TRX/startAtomic() */
- const TRANSACTION_INTERNAL = 'implicit';
-
- /** @var string Transaction operation comes from service managing all DBs */
- const FLUSHING_ALL_PEERS = 'flush';
- /** @var string Transaction operation comes from the database class internally */
- const FLUSHING_INTERNAL = 'flush';
-
- /** @var string Do not remember the prior flags */
- const REMEMBER_NOTHING = '';
- /** @var string Remember the prior flags */
- const REMEMBER_PRIOR = 'remember';
- /** @var string Restore to the prior flag state */
- const RESTORE_PRIOR = 'prior';
- /** @var string Restore to the initial flag state */
- const RESTORE_INITIAL = 'initial';
-
- /** @var string Estimate total time (RTT, scanning, waiting on locks, applying) */
- const ESTIMATE_TOTAL = 'total';
- /** @var string Estimate time to apply (scanning, applying) */
- const ESTIMATE_DB_APPLY = 'apply';
-
- /**
- * A string describing the current software version, and possibly
- * other details in a user-friendly way. Will be listed on Special:Version, etc.
- * Use getServerVersion() to get machine-friendly information.
- *
- * @return string Version information from the database server
- */
- public function getServerInfo();
-
- /**
- * Turns buffering of SQL result sets on (true) or off (false). Default is
- * "on".
- *
- * Unbuffered queries are very troublesome in MySQL:
- *
- * - If another query is executed while the first query is being read
- * out, the first query is killed. This means you can't call normal
- * MediaWiki functions while you are reading an unbuffered query result
- * from a normal wfGetDB() connection.
- *
- * - Unbuffered queries cause the MySQL server to use large amounts of
- * memory and to hold broad locks which block other queries.
- *
- * If you want to limit client-side memory, it's almost always better to
- * split up queries into batches using a LIMIT clause than to switch off
- * buffering.
- *
- * @param null|bool $buffer
- * @return null|bool The previous value of the flag
- */
- public function bufferResults( $buffer = null );
-
- /**
- * Gets the current transaction level.
- *
- * Historically, transactions were allowed to be "nested". This is no
- * longer supported, so this function really only returns a boolean.
- *
- * @return int The previous value
- */
- public function trxLevel();
-
- /**
- * Get the UNIX timestamp of the time that the transaction was established
- *
- * This can be used to reason about the staleness of SELECT data
- * in REPEATABLE-READ transaction isolation level.
- *
- * @return float|null Returns null if there is not active transaction
- * @since 1.25
- */
- public function trxTimestamp();
-
- /**
- * @return bool Whether an explicit transaction or atomic sections are still open
- * @since 1.28
- */
- public function explicitTrxActive();
-
- /**
- * Get/set the table prefix.
- * @param string $prefix The table prefix to set, or omitted to leave it unchanged.
- * @return string The previous table prefix.
- */
- public function tablePrefix( $prefix = null );
-
- /**
- * Get/set the db schema.
- * @param string $schema The database schema to set, or omitted to leave it unchanged.
- * @return string The previous db schema.
- */
- public function dbSchema( $schema = null );
-
- /**
- * Get properties passed down from the server info array of the load
- * balancer.
- *
- * @param string $name The entry of the info array to get, or null to get the
- * whole array
- *
- * @return array|mixed|null
- */
- public function getLBInfo( $name = null );
-
- /**
- * Set the LB info array, or a member of it. If called with one parameter,
- * the LB info array is set to that parameter. If it is called with two
- * parameters, the member with the given name is set to the given value.
- *
- * @param string $name
- * @param array $value
- */
- public function setLBInfo( $name, $value = null );
-
- /**
- * Returns true if this database does an implicit sort when doing GROUP BY
- *
- * @return bool
- */
- public function implicitGroupby();
-
- /**
- * Returns true if this database does an implicit order by when the column has an index
- * For example: SELECT page_title FROM page LIMIT 1
- *
- * @return bool
- */
- public function implicitOrderby();
-
- /**
- * Return the last query that went through IDatabase::query()
- * @return string
- */
- public function lastQuery();
-
- /**
- * Returns true if the connection may have been used for write queries.
- * Should return true if unsure.
- *
- * @return bool
- */
- public function doneWrites();
-
- /**
- * Returns the last time the connection may have been used for write queries.
- * Should return a timestamp if unsure.
- *
- * @return int|float UNIX timestamp or false
- * @since 1.24
- */
- public function lastDoneWrites();
-
- /**
- * @return bool Whether there is a transaction open with possible write queries
- * @since 1.27
- */
- public function writesPending();
-
- /**
- * Returns true if there is a transaction open with possible write
- * queries or transaction pre-commit/idle callbacks waiting on it to finish.
- * This does *not* count recurring callbacks, e.g. from setTransactionListener().
- *
- * @return bool
- */
- public function writesOrCallbacksPending();
-
- /**
- * Get the time spend running write queries for this transaction
- *
- * High times could be due to scanning, updates, locking, and such
- *
- * @param string $type IDatabase::ESTIMATE_* constant [default: ESTIMATE_ALL]
- * @return float|bool Returns false if not transaction is active
- * @since 1.26
- */
- public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL );
-
- /**
- * Get the list of method names that did write queries for this transaction
- *
- * @return array
- * @since 1.27
- */
- public function pendingWriteCallers();
-
- /**
- * Is a connection to the database open?
- * @return bool
- */
- public function isOpen();
-
- /**
- * Set a flag for this connection
- *
- * @param int $flag DBO_* constants from Defines.php:
- * - DBO_DEBUG: output some debug info (same as debug())
- * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
- * - DBO_TRX: automatically start transactions
- * - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode
- * and removes it in command line mode
- * - DBO_PERSISTENT: use persistant database connection
- * @param string $remember IDatabase::REMEMBER_* constant [default: REMEMBER_NOTHING]
- */
- public function setFlag( $flag, $remember = self::REMEMBER_NOTHING );
-
- /**
- * Clear a flag for this connection
- *
- * @param int $flag DBO_* constants from Defines.php:
- * - DBO_DEBUG: output some debug info (same as debug())
- * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
- * - DBO_TRX: automatically start transactions
- * - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode
- * and removes it in command line mode
- * - DBO_PERSISTENT: use persistant database connection
- * @param string $remember IDatabase::REMEMBER_* constant [default: REMEMBER_NOTHING]
- */
- public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING );
-
- /**
- * Restore the flags to their prior state before the last setFlag/clearFlag call
- *
- * @param string $state IDatabase::RESTORE_* constant. [default: RESTORE_PRIOR]
- * @since 1.28
- */
- public function restoreFlags( $state = self::RESTORE_PRIOR );
-
- /**
- * Returns a boolean whether the flag $flag is set for this connection
- *
- * @param int $flag DBO_* constants from Defines.php:
- * - DBO_DEBUG: output some debug info (same as debug())
- * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
- * - DBO_TRX: automatically start transactions
- * - DBO_PERSISTENT: use persistant database connection
- * @return bool
- */
- public function getFlag( $flag );
-
- /**
- * General read-only accessor
- *
- * @param string $name
- * @return string
- */
- public function getProperty( $name );
-
- /**
- * @return string
- */
- public function getWikiID();
-
- /**
- * Get the type of the DBMS, as it appears in $wgDBtype.
- *
- * @return string
- */
- public function getType();
-
- /**
- * Open a connection to the database. Usually aborts on failure
- *
- * @param string $server Database server host
- * @param string $user Database user name
- * @param string $password Database user password
- * @param string $dbName Database name
- * @return bool
- * @throws DBConnectionError
- */
- public function open( $server, $user, $password, $dbName );
-
- /**
- * Fetch the next row from the given result object, in object form.
- * Fields can be retrieved with $row->fieldname, with fields acting like
- * member variables.
- * If no more rows are available, false is returned.
- *
- * @param ResultWrapper|stdClass $res Object as returned from IDatabase::query(), etc.
- * @return stdClass|bool
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- public function fetchObject( $res );
-
- /**
- * Fetch the next row from the given result object, in associative array
- * form. Fields are retrieved with $row['fieldname'].
- * If no more rows are available, false is returned.
- *
- * @param ResultWrapper $res Result object as returned from IDatabase::query(), etc.
- * @return array|bool
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- public function fetchRow( $res );
-
- /**
- * Get the number of rows in a result object
- *
- * @param mixed $res A SQL result
- * @return int
- */
- public function numRows( $res );
-
- /**
- * Get the number of fields in a result object
- * @see http://www.php.net/mysql_num_fields
- *
- * @param mixed $res A SQL result
- * @return int
- */
- public function numFields( $res );
-
- /**
- * Get a field name in a result object
- * @see http://www.php.net/mysql_field_name
- *
- * @param mixed $res A SQL result
- * @param int $n
- * @return string
- */
- public function fieldName( $res, $n );
-
- /**
- * Get the inserted value of an auto-increment row
- *
- * The value inserted should be fetched from nextSequenceValue()
- *
- * Example:
- * $id = $dbw->nextSequenceValue( 'page_page_id_seq' );
- * $dbw->insert( 'page', [ 'page_id' => $id ] );
- * $id = $dbw->insertId();
- *
- * @return int
- */
- public function insertId();
-
- /**
- * Change the position of the cursor in a result object
- * @see http://www.php.net/mysql_data_seek
- *
- * @param mixed $res A SQL result
- * @param int $row
- */
- public function dataSeek( $res, $row );
-
- /**
- * Get the last error number
- * @see http://www.php.net/mysql_errno
- *
- * @return int
- */
- public function lastErrno();
-
- /**
- * Get a description of the last error
- * @see http://www.php.net/mysql_error
- *
- * @return string
- */
- public function lastError();
-
- /**
- * mysql_fetch_field() wrapper
- * Returns false if the field doesn't exist
- *
- * @param string $table Table name
- * @param string $field Field name
- *
- * @return Field
- */
- public function fieldInfo( $table, $field );
-
- /**
- * Get the number of rows affected by the last write query
- * @see http://www.php.net/mysql_affected_rows
- *
- * @return int
- */
- public function affectedRows();
-
- /**
- * Returns a wikitext link to the DB's website, e.g.,
- * return "[http://www.mysql.com/ MySQL]";
- * Should at least contain plain text, if for some reason
- * your database has no website.
- *
- * @return string Wikitext of a link to the server software's web site
- */
- public function getSoftwareLink();
-
- /**
- * A string describing the current software version, like from
- * mysql_get_server_info().
- *
- * @return string Version information from the database server.
- */
- public function getServerVersion();
-
- /**
- * Closes a database connection.
- * if it is open : commits any open transactions
- *
- * @throws MWException
- * @return bool Operation success. true if already closed.
- */
- public function close();
-
- /**
- * @param string $error Fallback error message, used if none is given by DB
- * @throws DBConnectionError
- */
- public function reportConnectionError( $error = 'Unknown error' );
-
- /**
- * Run an SQL query and return the result. Normally throws a DBQueryError
- * on failure. If errors are ignored, returns false instead.
- *
- * In new code, the query wrappers select(), insert(), update(), delete(),
- * etc. should be used where possible, since they give much better DBMS
- * independence and automatically quote or validate user input in a variety
- * of contexts. This function is generally only useful for queries which are
- * explicitly DBMS-dependent and are unsupported by the query wrappers, such
- * as CREATE TABLE.
- *
- * However, the query wrappers themselves should call this function.
- *
- * @param string $sql SQL query
- * @param string $fname Name of the calling function, for profiling/SHOW PROCESSLIST
- * comment (you can use __METHOD__ or add some extra info)
- * @param bool $tempIgnore Whether to avoid throwing an exception on errors...
- * maybe best to catch the exception instead?
- * @throws MWException
- * @return bool|ResultWrapper True for a successful write query, ResultWrapper object
- * for a successful read query, or false on failure if $tempIgnore set
- */
- public function query( $sql, $fname = __METHOD__, $tempIgnore = false );
-
- /**
- * Report a query error. Log the error, and if neither the object ignore
- * flag nor the $tempIgnore flag is set, throw a DBQueryError.
- *
- * @param string $error
- * @param int $errno
- * @param string $sql
- * @param string $fname
- * @param bool $tempIgnore
- * @throws DBQueryError
- */
- public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false );
-
- /**
- * Free a result object returned by query() or select(). It's usually not
- * necessary to call this, just use unset() or let the variable holding
- * the result object go out of scope.
- *
- * @param mixed $res A SQL result
- */
- public function freeResult( $res );
-
- /**
- * A SELECT wrapper which returns a single field from a single result row.
- *
- * Usually throws a DBQueryError on failure. If errors are explicitly
- * ignored, returns false on failure.
- *
- * If no result rows are returned from the query, false is returned.
- *
- * @param string|array $table Table name. See IDatabase::select() for details.
- * @param string $var The field name to select. This must be a valid SQL
- * fragment: do not use unvalidated user input.
- * @param string|array $cond The condition array. See IDatabase::select() for details.
- * @param string $fname The function name of the caller.
- * @param string|array $options The query options. See IDatabase::select() for details.
- *
- * @return bool|mixed The value from the field, or false on failure.
- */
- public function selectField(
- $table, $var, $cond = '', $fname = __METHOD__, $options = []
- );
-
- /**
- * A SELECT wrapper which returns a list of single field values from result rows.
- *
- * Usually throws a DBQueryError on failure. If errors are explicitly
- * ignored, returns false on failure.
- *
- * If no result rows are returned from the query, false is returned.
- *
- * @param string|array $table Table name. See IDatabase::select() for details.
- * @param string $var The field name to select. This must be a valid SQL
- * fragment: do not use unvalidated user input.
- * @param string|array $cond The condition array. See IDatabase::select() for details.
- * @param string $fname The function name of the caller.
- * @param string|array $options The query options. See IDatabase::select() for details.
- *
- * @return bool|array The values from the field, or false on failure
- * @since 1.25
- */
- public function selectFieldValues(
- $table, $var, $cond = '', $fname = __METHOD__, $options = []
- );
-
- /**
- * Execute a SELECT query constructed using the various parameters provided.
- * See below for full details of the parameters.
- *
- * @param string|array $table Table name
- * @param string|array $vars Field names
- * @param string|array $conds Conditions
- * @param string $fname Caller function name
- * @param array $options Query options
- * @param array $join_conds Join conditions
- *
- *
- * @param string|array $table
- *
- * May be either an array of table names, or a single string holding a table
- * name. If an array is given, table aliases can be specified, for example:
- *
- * [ 'a' => 'user' ]
- *
- * This includes the user table in the query, with the alias "a" available
- * for use in field names (e.g. a.user_name).
- *
- * All of the table names given here are automatically run through
- * DatabaseBase::tableName(), which causes the table prefix (if any) to be
- * added, and various other table name mappings to be performed.
- *
- * Do not use untrusted user input as a table name. Alias names should
- * not have characters outside of the Basic multilingual plane.
- *
- * @param string|array $vars
- *
- * May be either a field name or an array of field names. The field names
- * can be complete fragments of SQL, for direct inclusion into the SELECT
- * query. If an array is given, field aliases can be specified, for example:
- *
- * [ 'maxrev' => 'MAX(rev_id)' ]
- *
- * This includes an expression with the alias "maxrev" in the query.
- *
- * If an expression is given, care must be taken to ensure that it is
- * DBMS-independent.
- *
- * Untrusted user input must not be passed to this parameter.
- *
- * @param string|array $conds
- *
- * May be either a string containing a single condition, or an array of
- * conditions. If an array is given, the conditions constructed from each
- * element are combined with AND.
- *
- * Array elements may take one of two forms:
- *
- * - Elements with a numeric key are interpreted as raw SQL fragments.
- * - Elements with a string key are interpreted as equality conditions,
- * where the key is the field name.
- * - If the value of such an array element is a scalar (such as a
- * string), it will be treated as data and thus quoted appropriately.
- * If it is null, an IS NULL clause will be added.
- * - If the value is an array, an IN (...) clause will be constructed
- * from its non-null elements, and an IS NULL clause will be added
- * if null is present, such that the field may match any of the
- * elements in the array. The non-null elements will be quoted.
- *
- * Note that expressions are often DBMS-dependent in their syntax.
- * DBMS-independent wrappers are provided for constructing several types of
- * expression commonly used in condition queries. See:
- * - IDatabase::buildLike()
- * - IDatabase::conditional()
- *
- * Untrusted user input is safe in the values of string keys, however untrusted
- * input must not be used in the array key names or in the values of numeric keys.
- * Escaping of untrusted input used in values of numeric keys should be done via
- * IDatabase::addQuotes()
- *
- * @param string|array $options
- *
- * Optional: Array of query options. Boolean options are specified by
- * including them in the array as a string value with a numeric key, for
- * example:
- *
- * [ 'FOR UPDATE' ]
- *
- * The supported options are:
- *
- * - OFFSET: Skip this many rows at the start of the result set. OFFSET
- * with LIMIT can theoretically be used for paging through a result set,
- * but this is discouraged in MediaWiki for performance reasons.
- *
- * - LIMIT: Integer: return at most this many rows. The rows are sorted
- * and then the first rows are taken until the limit is reached. LIMIT
- * is applied to a result set after OFFSET.
- *
- * - FOR UPDATE: Boolean: lock the returned rows so that they can't be
- * changed until the next COMMIT.
- *
- * - DISTINCT: Boolean: return only unique result rows.
- *
- * - GROUP BY: May be either an SQL fragment string naming a field or
- * expression to group by, or an array of such SQL fragments.
- *
- * - HAVING: May be either an string containing a HAVING clause or an array of
- * conditions building the HAVING clause. If an array is given, the conditions
- * constructed from each element are combined with AND.
- *
- * - ORDER BY: May be either an SQL fragment giving a field name or
- * expression to order by, or an array of such SQL fragments.
- *
- * - USE INDEX: This may be either a string giving the index name to use
- * for the query, or an array. If it is an associative array, each key
- * gives the table name (or alias), each value gives the index name to
- * use for that table. All strings are SQL fragments and so should be
- * validated by the caller.
- *
- * - EXPLAIN: In MySQL, this causes an EXPLAIN SELECT query to be run,
- * instead of SELECT.
- *
- * And also the following boolean MySQL extensions, see the MySQL manual
- * for documentation:
- *
- * - LOCK IN SHARE MODE
- * - STRAIGHT_JOIN
- * - HIGH_PRIORITY
- * - SQL_BIG_RESULT
- * - SQL_BUFFER_RESULT
- * - SQL_SMALL_RESULT
- * - SQL_CALC_FOUND_ROWS
- * - SQL_CACHE
- * - SQL_NO_CACHE
- *
- *
- * @param string|array $join_conds
- *
- * Optional associative array of table-specific join conditions. In the
- * most common case, this is unnecessary, since the join condition can be
- * in $conds. However, it is useful for doing a LEFT JOIN.
- *
- * The key of the array contains the table name or alias. The value is an
- * array with two elements, numbered 0 and 1. The first gives the type of
- * join, the second is the same as the $conds parameter. Thus it can be
- * an SQL fragment, or an array where the string keys are equality and the
- * numeric keys are SQL fragments all AND'd together. For example:
- *
- * [ 'page' => [ 'LEFT JOIN', 'page_latest=rev_id' ] ]
- *
- * @return ResultWrapper|bool If the query returned no rows, a ResultWrapper
- * with no rows in it will be returned. If there was a query error, a
- * DBQueryError exception will be thrown, except if the "ignore errors"
- * option was set, in which case false will be returned.
- */
- public function select(
- $table, $vars, $conds = '', $fname = __METHOD__,
- $options = [], $join_conds = []
- );
-
- /**
- * The equivalent of IDatabase::select() except that the constructed SQL
- * is returned, instead of being immediately executed. This can be useful for
- * doing UNION queries, where the SQL text of each query is needed. In general,
- * however, callers outside of Database classes should just use select().
- *
- * @param string|array $table Table name
- * @param string|array $vars Field names
- * @param string|array $conds Conditions
- * @param string $fname Caller function name
- * @param string|array $options Query options
- * @param string|array $join_conds Join conditions
- *
- * @return string SQL query string.
- * @see IDatabase::select()
- */
- public function selectSQLText(
- $table, $vars, $conds = '', $fname = __METHOD__,
- $options = [], $join_conds = []
- );
-
- /**
- * Single row SELECT wrapper. Equivalent to IDatabase::select(), except
- * that a single row object is returned. If the query returns no rows,
- * false is returned.
- *
- * @param string|array $table Table name
- * @param string|array $vars Field names
- * @param array $conds Conditions
- * @param string $fname Caller function name
- * @param string|array $options Query options
- * @param array|string $join_conds Join conditions
- *
- * @return stdClass|bool
- */
- public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
- $options = [], $join_conds = []
- );
-
- /**
- * Estimate the number of rows in dataset
- *
- * MySQL allows you to estimate the number of rows that would be returned
- * by a SELECT query, using EXPLAIN SELECT. The estimate is provided using
- * index cardinality statistics, and is notoriously inaccurate, especially
- * when large numbers of rows have recently been added or deleted.
- *
- * For DBMSs that don't support fast result size estimation, this function
- * will actually perform the SELECT COUNT(*).
- *
- * Takes the same arguments as IDatabase::select().
- *
- * @param string $table Table name
- * @param string $vars Unused
- * @param array|string $conds Filters on the table
- * @param string $fname Function name for profiling
- * @param array $options Options for select
- * @return int Row count
- */
- public function estimateRowCount(
- $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = []
- );
-
- /**
- * Get the number of rows in dataset
- *
- * This is useful when trying to do COUNT(*) but with a LIMIT for performance.
- *
- * Takes the same arguments as IDatabase::select().
- *
- * @since 1.27 Added $join_conds parameter
- *
- * @param array|string $tables Table names
- * @param string $vars Unused
- * @param array|string $conds Filters on the table
- * @param string $fname Function name for profiling
- * @param array $options Options for select
- * @param array $join_conds Join conditions (since 1.27)
- * @return int Row count
- */
- public function selectRowCount(
- $tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
- );
-
- /**
- * Determines whether a field exists in a table
- *
- * @param string $table Table name
- * @param string $field Filed to check on that table
- * @param string $fname Calling function name (optional)
- * @return bool Whether $table has filed $field
- */
- public function fieldExists( $table, $field, $fname = __METHOD__ );
-
- /**
- * Determines whether an index exists
- * Usually throws a DBQueryError on failure
- * If errors are explicitly ignored, returns NULL on failure
- *
- * @param string $table
- * @param string $index
- * @param string $fname
- * @return bool|null
- */
- public function indexExists( $table, $index, $fname = __METHOD__ );
-
- /**
- * Query whether a given table exists
- *
- * @param string $table
- * @param string $fname
- * @return bool
- */
- public function tableExists( $table, $fname = __METHOD__ );
-
- /**
- * Determines if a given index is unique
- *
- * @param string $table
- * @param string $index
- *
- * @return bool
- */
- public function indexUnique( $table, $index );
-
- /**
- * INSERT wrapper, inserts an array into a table.
- *
- * $a may be either:
- *
- * - A single associative array. The array keys are the field names, and
- * the values are the values to insert. The values are treated as data
- * and will be quoted appropriately. If NULL is inserted, this will be
- * converted to a database NULL.
- * - An array with numeric keys, holding a list of associative arrays.
- * This causes a multi-row INSERT on DBMSs that support it. The keys in
- * each subarray must be identical to each other, and in the same order.
- *
- * Usually throws a DBQueryError on failure. If errors are explicitly ignored,
- * returns success.
- *
- * $options is an array of options, with boolean options encoded as values
- * with numeric keys, in the same style as $options in
- * IDatabase::select(). Supported options are:
- *
- * - IGNORE: Boolean: if present, duplicate key errors are ignored, and
- * any rows which cause duplicate key errors are not inserted. It's
- * possible to determine how many rows were successfully inserted using
- * IDatabase::affectedRows().
- *
- * @param string $table Table name. This will be passed through
- * DatabaseBase::tableName().
- * @param array $a Array of rows to insert
- * @param string $fname Calling function name (use __METHOD__) for logs/profiling
- * @param array $options Array of options
- *
- * @return bool
- */
- public function insert( $table, $a, $fname = __METHOD__, $options = [] );
-
- /**
- * UPDATE wrapper. Takes a condition array and a SET array.
- *
- * @param string $table Name of the table to UPDATE. This will be passed through
- * DatabaseBase::tableName().
- * @param array $values An array of values to SET. For each array element,
- * the key gives the field name, and the value gives the data to set
- * that field to. The data will be quoted by IDatabase::addQuotes().
- * @param array $conds An array of conditions (WHERE). See
- * IDatabase::select() for the details of the format of condition
- * arrays. Use '*' to update all rows.
- * @param string $fname The function name of the caller (from __METHOD__),
- * for logging and profiling.
- * @param array $options An array of UPDATE options, can be:
- * - IGNORE: Ignore unique key conflicts
- * - LOW_PRIORITY: MySQL-specific, see MySQL manual.
- * @return bool
- */
- public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] );
-
- /**
- * Makes an encoded list of strings from an array
- *
- * @param array $a Containing the data
- * @param int $mode Constant
- * - LIST_COMMA: Comma separated, no field names
- * - LIST_AND: ANDed WHERE clause (without the WHERE). See the
- * documentation for $conds in IDatabase::select().
- * - LIST_OR: ORed WHERE clause (without the WHERE)
- * - LIST_SET: Comma separated with field names, like a SET clause
- * - LIST_NAMES: Comma separated field names
- * @throws MWException|DBUnexpectedError
- * @return string
- */
- public function makeList( $a, $mode = LIST_COMMA );
-
- /**
- * Build a partial where clause from a 2-d array such as used for LinkBatch.
- * The keys on each level may be either integers or strings.
- *
- * @param array $data Organized as 2-d
- * [ baseKeyVal => [ subKeyVal => [ignored], ... ], ... ]
- * @param string $baseKey Field name to match the base-level keys to (eg 'pl_namespace')
- * @param string $subKey Field name to match the sub-level keys to (eg 'pl_title')
- * @return string|bool SQL fragment, or false if no items in array
- */
- public function makeWhereFrom2d( $data, $baseKey, $subKey );
-
- /**
- * @param string $field
- * @return string
- */
- public function bitNot( $field );
-
- /**
- * @param string $fieldLeft
- * @param string $fieldRight
- * @return string
- */
- public function bitAnd( $fieldLeft, $fieldRight );
-
- /**
- * @param string $fieldLeft
- * @param string $fieldRight
- * @return string
- */
- public function bitOr( $fieldLeft, $fieldRight );
-
- /**
- * Build a concatenation list to feed into a SQL query
- * @param array $stringList List of raw SQL expressions; caller is
- * responsible for any quoting
- * @return string
- */
- public function buildConcat( $stringList );
-
- /**
- * Build a GROUP_CONCAT or equivalent statement for a query.
- *
- * This is useful for combining a field for several rows into a single string.
- * NULL values will not appear in the output, duplicated values will appear,
- * and the resulting delimiter-separated values have no defined sort order.
- * Code using the results may need to use the PHP unique() or sort() methods.
- *
- * @param string $delim Glue to bind the results together
- * @param string|array $table Table name
- * @param string $field Field name
- * @param string|array $conds Conditions
- * @param string|array $join_conds Join conditions
- * @return string SQL text
- * @since 1.23
- */
- public function buildGroupConcatField(
- $delim, $table, $field, $conds = '', $join_conds = []
- );
-
- /**
- * Change the current database
- *
- * @param string $db
- * @return bool Success or failure
- */
- public function selectDB( $db );
-
- /**
- * Get the current DB name
- * @return string
- */
- public function getDBname();
-
- /**
- * Get the server hostname or IP address
- * @return string
- */
- public function getServer();
-
- /**
- * Adds quotes and backslashes.
- *
- * @param string|Blob $s
- * @return string
- */
- public function addQuotes( $s );
-
- /**
- * LIKE statement wrapper, receives a variable-length argument list with
- * parts of pattern to match containing either string literals that will be
- * escaped or tokens returned by anyChar() or anyString(). Alternatively,
- * the function could be provided with an array of aforementioned
- * parameters.
- *
- * Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns
- * a LIKE clause that searches for subpages of 'My page title'.
- * Alternatively:
- * $pattern = [ 'My_page_title/', $dbr->anyString() ];
- * $query .= $dbr->buildLike( $pattern );
- *
- * @since 1.16
- * @return string Fully built LIKE statement
- */
- public function buildLike();
-
- /**
- * Returns a token for buildLike() that denotes a '_' to be used in a LIKE query
- *
- * @return LikeMatch
- */
- public function anyChar();
-
- /**
- * Returns a token for buildLike() that denotes a '%' to be used in a LIKE query
- *
- * @return LikeMatch
- */
- public function anyString();
-
- /**
- * Returns an appropriately quoted sequence value for inserting a new row.
- * MySQL has autoincrement fields, so this is just NULL. But the PostgreSQL
- * subclass will return an integer, and save the value for insertId()
- *
- * Any implementation of this function should *not* involve reusing
- * sequence numbers created for rolled-back transactions.
- * See http://bugs.mysql.com/bug.php?id=30767 for details.
- * @param string $seqName
- * @return null|int
- */
- public function nextSequenceValue( $seqName );
-
- /**
- * REPLACE query wrapper.
- *
- * REPLACE is a very handy MySQL extension, which functions like an INSERT
- * except that when there is a duplicate key error, the old row is deleted
- * and the new row is inserted in its place.
- *
- * We simulate this with standard SQL with a DELETE followed by INSERT. To
- * perform the delete, we need to know what the unique indexes are so that
- * we know how to find the conflicting rows.
- *
- * It may be more efficient to leave off unique indexes which are unlikely
- * to collide. However if you do this, you run the risk of encountering
- * errors which wouldn't have occurred in MySQL.
- *
- * @param string $table The table to replace the row(s) in.
- * @param array $uniqueIndexes Is an array of indexes. Each element may be either
- * a field name or an array of field names
- * @param array $rows Can be either a single row to insert, or multiple rows,
- * in the same format as for IDatabase::insert()
- * @param string $fname Calling function name (use __METHOD__) for logs/profiling
- */
- public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ );
-
- /**
- * INSERT ON DUPLICATE KEY UPDATE wrapper, upserts an array into a table.
- *
- * This updates any conflicting rows (according to the unique indexes) using
- * the provided SET clause and inserts any remaining (non-conflicted) rows.
- *
- * $rows may be either:
- * - A single associative array. The array keys are the field names, and
- * the values are the values to insert. The values are treated as data
- * and will be quoted appropriately. If NULL is inserted, this will be
- * converted to a database NULL.
- * - An array with numeric keys, holding a list of associative arrays.
- * This causes a multi-row INSERT on DBMSs that support it. The keys in
- * each subarray must be identical to each other, and in the same order.
- *
- * It may be more efficient to leave off unique indexes which are unlikely
- * to collide. However if you do this, you run the risk of encountering
- * errors which wouldn't have occurred in MySQL.
- *
- * Usually throws a DBQueryError on failure. If errors are explicitly ignored,
- * returns success.
- *
- * @since 1.22
- *
- * @param string $table Table name. This will be passed through DatabaseBase::tableName().
- * @param array $rows A single row or list of rows to insert
- * @param array $uniqueIndexes List of single field names or field name tuples
- * @param array $set An array of values to SET. For each array element, the
- * key gives the field name, and the value gives the data to set that
- * field to. The data will be quoted by IDatabase::addQuotes().
- * @param string $fname Calling function name (use __METHOD__) for logs/profiling
- * @throws Exception
- * @return bool
- */
- public function upsert(
- $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__
- );
-
- /**
- * DELETE where the condition is a join.
- *
- * MySQL overrides this to use a multi-table DELETE syntax, in other databases
- * we use sub-selects
- *
- * For safety, an empty $conds will not delete everything. If you want to
- * delete all rows where the join condition matches, set $conds='*'.
- *
- * DO NOT put the join condition in $conds.
- *
- * @param string $delTable The table to delete from.
- * @param string $joinTable The other table.
- * @param string $delVar The variable to join on, in the first table.
- * @param string $joinVar The variable to join on, in the second table.
- * @param array $conds Condition array of field names mapped to variables,
- * ANDed together in the WHERE clause
- * @param string $fname Calling function name (use __METHOD__) for logs/profiling
- * @throws DBUnexpectedError
- */
- public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
- $fname = __METHOD__
- );
-
- /**
- * DELETE query wrapper.
- *
- * @param array $table Table name
- * @param string|array $conds Array of conditions. See $conds in IDatabase::select()
- * for the format. Use $conds == "*" to delete all rows
- * @param string $fname Name of the calling function
- * @throws DBUnexpectedError
- * @return bool|ResultWrapper
- */
- public function delete( $table, $conds, $fname = __METHOD__ );
-
- /**
- * INSERT SELECT wrapper. Takes data from a SELECT query and inserts it
- * into another table.
- *
- * @param string $destTable The table name to insert into
- * @param string|array $srcTable May be either a table name, or an array of table names
- * to include in a join.
- *
- * @param array $varMap Must be an associative array of the form
- * [ 'dest1' => 'source1', ... ]. Source items may be literals
- * rather than field names, but strings should be quoted with
- * IDatabase::addQuotes()
- *
- * @param array $conds Condition array. See $conds in IDatabase::select() for
- * the details of the format of condition arrays. May be "*" to copy the
- * whole table.
- *
- * @param string $fname The function name of the caller, from __METHOD__
- *
- * @param array $insertOptions Options for the INSERT part of the query, see
- * IDatabase::insert() for details.
- * @param array $selectOptions Options for the SELECT part of the query, see
- * IDatabase::select() for details.
- *
- * @return ResultWrapper
- */
- public function insertSelect( $destTable, $srcTable, $varMap, $conds,
- $fname = __METHOD__,
- $insertOptions = [], $selectOptions = []
- );
-
- /**
- * Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries
- * within the UNION construct.
- * @return bool
- */
- public function unionSupportsOrderAndLimit();
-
- /**
- * Construct a UNION query
- * This is used for providing overload point for other DB abstractions
- * not compatible with the MySQL syntax.
- * @param array $sqls SQL statements to combine
- * @param bool $all Use UNION ALL
- * @return string SQL fragment
- */
- public function unionQueries( $sqls, $all );
-
- /**
- * Returns an SQL expression for a simple conditional. This doesn't need
- * to be overridden unless CASE isn't supported in your DBMS.
- *
- * @param string|array $cond SQL expression which will result in a boolean value
- * @param string $trueVal SQL expression to return if true
- * @param string $falseVal SQL expression to return if false
- * @return string SQL fragment
- */
- public function conditional( $cond, $trueVal, $falseVal );
-
- /**
- * Returns a comand for str_replace function in SQL query.
- * Uses REPLACE() in MySQL
- *
- * @param string $orig Column to modify
- * @param string $old Column to seek
- * @param string $new Column to replace with
- *
- * @return string
- */
- public function strreplace( $orig, $old, $new );
-
- /**
- * Determines how long the server has been up
- *
- * @return int
- */
- public function getServerUptime();
-
- /**
- * Determines if the last failure was due to a deadlock
- *
- * @return bool
- */
- public function wasDeadlock();
-
- /**
- * Determines if the last failure was due to a lock timeout
- *
- * @return bool
- */
- public function wasLockTimeout();
-
- /**
- * Determines if the last query error was due to a dropped connection and should
- * be dealt with by pinging the connection and reissuing the query.
- *
- * @return bool
- */
- public function wasErrorReissuable();
-
- /**
- * Determines if the last failure was due to the database being read-only.
- *
- * @return bool
- */
- public function wasReadOnlyError();
-
- /**
- * Wait for the replica DB to catch up to a given master position
- *
- * @param DBMasterPos $pos
- * @param int $timeout The maximum number of seconds to wait for synchronisation
- * @return int|null Zero if the replica DB was past that position already,
- * greater than zero if we waited for some period of time, less than
- * zero if it timed out, and null on error
- */
- public function masterPosWait( DBMasterPos $pos, $timeout );
-
- /**
- * Get the replication position of this replica DB
- *
- * @return DBMasterPos|bool False if this is not a replica DB.
- */
- public function getSlavePos();
-
- /**
- * Get the position of this master
- *
- * @return DBMasterPos|bool False if this is not a master
- */
- public function getMasterPos();
-
- /**
- * @return bool Whether the DB is marked as read-only server-side
- * @since 1.28
- */
- public function serverIsReadOnly();
-
- /**
- * Run a callback as soon as the current transaction commits or rolls back.
- * An error is thrown if no transaction is pending. Queries in the function will run in
- * AUTO-COMMIT mode unless there are begin() calls. Callbacks must commit any transactions
- * that they begin.
- *
- * This is useful for combining cooperative locks and DB transactions.
- *
- * The callback takes one argument:
- * - How the transaction ended (IDatabase::TRIGGER_COMMIT or IDatabase::TRIGGER_ROLLBACK)
- *
- * @param callable $callback
- * @return mixed
- * @since 1.28
- */
- public function onTransactionResolution( callable $callback );
-
- /**
- * Run a callback as soon as there is no transaction pending.
- * If there is a transaction and it is rolled back, then the callback is cancelled.
- * Queries in the function will run in AUTO-COMMIT mode unless there are begin() calls.
- * Callbacks must commit any transactions that they begin.
- *
- * This is useful for updates to different systems or when separate transactions are needed.
- * For example, one might want to enqueue jobs into a system outside the database, but only
- * after the database is updated so that the jobs will see the data when they actually run.
- * It can also be used for updates that easily cause deadlocks if locks are held too long.
- *
- * Updates will execute in the order they were enqueued.
- *
- * The callback takes one argument:
- * - How the transaction ended (IDatabase::TRIGGER_COMMIT or IDatabase::TRIGGER_IDLE)
- *
- * @param callable $callback
- * @since 1.20
- */
- public function onTransactionIdle( callable $callback );
-
- /**
- * Run a callback before the current transaction commits or now if there is none.
- * If there is a transaction and it is rolled back, then the callback is cancelled.
- * Callbacks must not start nor commit any transactions. If no transaction is active,
- * then a transaction will wrap the callback.
- *
- * This is useful for updates that easily cause deadlocks if locks are held too long
- * but where atomicity is strongly desired for these updates and some related updates.
- *
- * Updates will execute in the order they were enqueued.
- *
- * @param callable $callback
- * @since 1.22
- */
- public function onTransactionPreCommitOrIdle( callable $callback );
-
- /**
- * Run a callback each time any transaction commits or rolls back
- *
- * The callback takes two arguments:
- * - IDatabase::TRIGGER_COMMIT or IDatabase::TRIGGER_ROLLBACK
- * - This IDatabase object
- * Callbacks must commit any transactions that they begin.
- *
- * Registering a callback here will not affect writesOrCallbacks() pending
- *
- * @param string $name Callback name
- * @param callable|null $callback Use null to unset a listener
- * @return mixed
- * @since 1.28
- */
- public function setTransactionListener( $name, callable $callback = null );
-
- /**
- * Begin an atomic section of statements
- *
- * If a transaction has been started already, just keep track of the given
- * section name to make sure the transaction is not committed pre-maturely.
- * This function can be used in layers (with sub-sections), so use a stack
- * to keep track of the different atomic sections. If there is no transaction,
- * start one implicitly.
- *
- * The goal of this function is to create an atomic section of SQL queries
- * without having to start a new transaction if it already exists.
- *
- * Atomic sections are more strict than transactions. With transactions,
- * attempting to begin a new transaction when one is already running results
- * in MediaWiki issuing a brief warning and doing an implicit commit. All
- * atomic levels *must* be explicitly closed using IDatabase::endAtomic(),
- * and any database transactions cannot be began or committed until all atomic
- * levels are closed. There is no such thing as implicitly opening or closing
- * an atomic section.
- *
- * @since 1.23
- * @param string $fname
- * @throws DBError
- */
- public function startAtomic( $fname = __METHOD__ );
-
- /**
- * Ends an atomic section of SQL statements
- *
- * Ends the next section of atomic SQL statements and commits the transaction
- * if necessary.
- *
- * @since 1.23
- * @see IDatabase::startAtomic
- * @param string $fname
- * @throws DBError
- */
- public function endAtomic( $fname = __METHOD__ );
-
- /**
- * Run a callback to do an atomic set of updates for this database
- *
- * The $callback takes the following arguments:
- * - This database object
- * - The value of $fname
- *
- * If any exception occurs in the callback, then rollback() will be called and the error will
- * be re-thrown. It may also be that the rollback itself fails with an exception before then.
- * In any case, such errors are expected to terminate the request, without any outside caller
- * attempting to catch errors and commit anyway. Note that any rollback undoes all prior
- * atomic section and uncommitted updates, which trashes the current request, requiring an
- * error to be displayed.
- *
- * This can be an alternative to explicit startAtomic()/endAtomic() calls.
- *
- * @see DatabaseBase::startAtomic
- * @see DatabaseBase::endAtomic
- *
- * @param string $fname Caller name (usually __METHOD__)
- * @param callable $callback Callback that issues DB updates
- * @return mixed $res Result of the callback (since 1.28)
- * @throws DBError
- * @throws RuntimeException
- * @throws UnexpectedValueException
- * @since 1.27
- */
- public function doAtomicSection( $fname, callable $callback );
-
- /**
- * Begin a transaction. If a transaction is already in progress,
- * that transaction will be committed before the new transaction is started.
- *
- * Only call this from code with outer transcation scope.
- * See https://www.mediawiki.org/wiki/Database_transactions for details.
- * Nesting of transactions is not supported.
- *
- * Note that when the DBO_TRX flag is set (which is usually the case for web
- * requests, but not for maintenance scripts), any previous database query
- * will have started a transaction automatically.
- *
- * Nesting of transactions is not supported. Attempts to nest transactions
- * will cause a warning, unless the current transaction was started
- * automatically because of the DBO_TRX flag.
- *
- * @param string $fname Calling function name
- * @param string $mode A situationally valid IDatabase::TRANSACTION_* constant [optional]
- * @throws DBError
- */
- public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT );
-
- /**
- * Commits a transaction previously started using begin().
- * If no transaction is in progress, a warning is issued.
- *
- * Only call this from code with outer transcation scope.
- * See https://www.mediawiki.org/wiki/Database_transactions for details.
- * Nesting of transactions is not supported.
- *
- * @param string $fname
- * @param string $flush Flush flag, set to situationally valid IDatabase::FLUSHING_*
- * constant to disable warnings about explicitly committing implicit transactions,
- * or calling commit when no transaction is in progress.
- *
- * This will trigger an exception if there is an ongoing explicit transaction.
- *
- * Only set the flush flag if you are sure that these warnings are not applicable,
- * and no explicit transactions are open.
- *
- * @throws DBUnexpectedError
- */
- public function commit( $fname = __METHOD__, $flush = '' );
-
- /**
- * Rollback a transaction previously started using begin().
- * If no transaction is in progress, a warning is issued.
- *
- * Only call this from code with outer transcation scope.
- * See https://www.mediawiki.org/wiki/Database_transactions for details.
- * Nesting of transactions is not supported. If a serious unexpected error occurs,
- * throwing an Exception is preferrable, using a pre-installed error handler to trigger
- * rollback (in any case, failure to issue COMMIT will cause rollback server-side).
- *
- * @param string $fname Calling function name
- * @param string $flush Flush flag, set to a situationally valid IDatabase::FLUSHING_*
- * constant to disable warnings about calling rollback when no transaction is in
- * progress. This will silently break any ongoing explicit transaction. Only set the
- * flush flag if you are sure that it is safe to ignore these warnings in your context.
- * @throws DBUnexpectedError
- * @since 1.23 Added $flush parameter
- */
- public function rollback( $fname = __METHOD__, $flush = '' );
-
- /**
- * Commit any transaction but error out if writes or callbacks are pending
- *
- * This is intended for clearing out REPEATABLE-READ snapshots so that callers can
- * see a new point-in-time of the database. This is useful when one of many transaction
- * rounds finished and significant time will pass in the script's lifetime. It is also
- * useful to call on a replica DB after waiting on replication to catch up to the master.
- *
- * @param string $fname Calling function name
- * @throws DBUnexpectedError
- * @since 1.28
- */
- public function flushSnapshot( $fname = __METHOD__ );
-
- /**
- * List all tables on the database
- *
- * @param string $prefix Only show tables with this prefix, e.g. mw_
- * @param string $fname Calling function name
- * @throws MWException
- * @return array
- */
- public function listTables( $prefix = null, $fname = __METHOD__ );
-
- /**
- * Convert a timestamp in one of the formats accepted by wfTimestamp()
- * to the format used for inserting into timestamp fields in this DBMS.
- *
- * The result is unquoted, and needs to be passed through addQuotes()
- * before it can be included in raw SQL.
- *
- * @param string|int $ts
- *
- * @return string
- */
- public function timestamp( $ts = 0 );
-
- /**
- * Convert a timestamp in one of the formats accepted by wfTimestamp()
- * to the format used for inserting into timestamp fields in this DBMS. If
- * NULL is input, it is passed through, allowing NULL values to be inserted
- * into timestamp fields.
- *
- * The result is unquoted, and needs to be passed through addQuotes()
- * before it can be included in raw SQL.
- *
- * @param string|int $ts
- *
- * @return string
- */
- public function timestampOrNull( $ts = null );
-
- /**
- * Ping the server and try to reconnect if it there is no connection
- *
- * @param float|null &$rtt Value to store the estimated RTT [optional]
- * @return bool Success or failure
- */
- public function ping( &$rtt = null );
-
- /**
- * Get replica DB lag. Currently supported only by MySQL.
- *
- * Note that this function will generate a fatal error on many
- * installations. Most callers should use LoadBalancer::safeGetLag()
- * instead.
- *
- * @return int|bool Database replication lag in seconds or false on error
- */
- public function getLag();
-
- /**
- * Get the replica DB lag when the current transaction started
- * or a general lag estimate if not transaction is active
- *
- * This is useful when transactions might use snapshot isolation
- * (e.g. REPEATABLE-READ in innodb), so the "real" lag of that data
- * is this lag plus transaction duration. If they don't, it is still
- * safe to be pessimistic. In AUTO-COMMIT mode, this still gives an
- * indication of the staleness of subsequent reads.
- *
- * @return array ('lag': seconds or false on error, 'since': UNIX timestamp of BEGIN)
- * @since 1.27
- */
- public function getSessionLagStatus();
-
- /**
- * Return the maximum number of items allowed in a list, or 0 for unlimited.
- *
- * @return int
- */
- public function maxListLen();
-
- /**
- * Some DBMSs have a special format for inserting into blob fields, they
- * don't allow simple quoted strings to be inserted. To insert into such
- * a field, pass the data through this function before passing it to
- * IDatabase::insert().
- *
- * @param string $b
- * @return string
- */
- public function encodeBlob( $b );
-
- /**
- * Some DBMSs return a special placeholder object representing blob fields
- * in result objects. Pass the object through this function to return the
- * original string.
- *
- * @param string|Blob $b
- * @return string
- */
- public function decodeBlob( $b );
-
- /**
- * Override database's default behavior. $options include:
- * 'connTimeout' : Set the connection timeout value in seconds.
- * May be useful for very long batch queries such as
- * full-wiki dumps, where a single query reads out over
- * hours or days.
- *
- * @param array $options
- * @return void
- */
- public function setSessionOptions( array $options );
-
- /**
- * Set variables to be used in sourceFile/sourceStream, in preference to the
- * ones in $GLOBALS. If an array is set here, $GLOBALS will not be used at
- * all. If it's set to false, $GLOBALS will be used.
- *
- * @param bool|array $vars Mapping variable name to value.
- */
- public function setSchemaVars( $vars );
-
- /**
- * Check to see if a named lock is available (non-blocking)
- *
- * @param string $lockName Name of lock to poll
- * @param string $method Name of method calling us
- * @return bool
- * @since 1.20
- */
- public function lockIsFree( $lockName, $method );
-
- /**
- * Acquire a named lock
- *
- * Named locks are not related to transactions
- *
- * @param string $lockName Name of lock to aquire
- * @param string $method Name of the calling method
- * @param int $timeout Acquisition timeout in seconds
- * @return bool
- */
- public function lock( $lockName, $method, $timeout = 5 );
-
- /**
- * Release a lock
- *
- * Named locks are not related to transactions
- *
- * @param string $lockName Name of lock to release
- * @param string $method Name of the calling method
- *
- * @return int Returns 1 if the lock was released, 0 if the lock was not established
- * by this thread (in which case the lock is not released), and NULL if the named
- * lock did not exist
- */
- public function unlock( $lockName, $method );
-
- /**
- * Acquire a named lock, flush any transaction, and return an RAII style unlocker object
- *
- * Only call this from outer transcation scope and when only one DB will be affected.
- * See https://www.mediawiki.org/wiki/Database_transactions for details.
- *
- * This is suitiable for transactions that need to be serialized using cooperative locks,
- * where each transaction can see each others' changes. Any transaction is flushed to clear
- * out stale REPEATABLE-READ snapshot data. Once the returned object falls out of PHP scope,
- * the lock will be released unless a transaction is active. If one is active, then the lock
- * will be released when it either commits or rolls back.
- *
- * If the lock acquisition failed, then no transaction flush happens, and null is returned.
- *
- * @param string $lockKey Name of lock to release
- * @param string $fname Name of the calling method
- * @param int $timeout Acquisition timeout in seconds
- * @return ScopedCallback|null
- * @throws DBUnexpectedError
- * @since 1.27
- */
- public function getScopedLockAndFlush( $lockKey, $fname, $timeout );
-
- /**
- * Check to see if a named lock used by lock() use blocking queues
- *
- * @return bool
- * @since 1.26
- */
- public function namedLocksEnqueue();
-
- /**
- * Find out when 'infinity' is. Most DBMSes support this. This is a special
- * keyword for timestamps in PostgreSQL, and works with CHAR(14) as well
- * because "i" sorts after all numbers.
- *
- * @return string
- */
- public function getInfinity();
-
- /**
- * Encode an expiry time into the DBMS dependent format
- *
- * @param string $expiry Timestamp for expiry, or the 'infinity' string
- * @return string
- */
- public function encodeExpiry( $expiry );
-
- /**
- * Decode an expiry time into a DBMS independent format
- *
- * @param string $expiry DB timestamp field value for expiry
- * @param int $format TS_* constant, defaults to TS_MW
- * @return string
- */
- public function decodeExpiry( $expiry, $format = TS_MW );
-
- /**
- * Allow or deny "big selects" for this session only. This is done by setting
- * the sql_big_selects session variable.
- *
- * This is a MySQL-specific feature.
- *
- * @param bool|string $value True for allow, false for deny, or "default" to
- * restore the initial value
- */
- public function setBigSelects( $value = true );
-
- /**
- * @return bool Whether this DB is read-only
- * @since 1.27
- */
- public function isReadOnly();
-}
* @ingroup Database
*/
+use Psr\Log\LoggerInterface;
use MediaWiki\MediaWikiServices;
use MediaWiki\Services\DestructibleService;
-use Psr\Log\LoggerInterface;
use MediaWiki\Logger\LoggerFactory;
/**
protected $trxProfiler;
/** @var LoggerInterface */
protected $trxLogger;
+ /** @var LoggerInterface */
+ protected $replLogger;
/** @var BagOStuff */
protected $srvCache;
+ /** @var BagOStuff */
+ protected $memCache;
/** @var WANObjectCache */
protected $wanCache;
/** @var callable[] */
protected $replicationWaitCallbacks = [];
- const SHUTDOWN_NO_CHRONPROT = 1; // don't save ChronologyProtector positions (for async code)
+ const SHUTDOWN_NO_CHRONPROT = 0; // don't save DB positions at all
+ const SHUTDOWN_CHRONPROT_ASYNC = 1; // save DB positions, but don't wait on remote DCs
+ const SHUTDOWN_CHRONPROT_SYNC = 2; // save DB positions, waiting on all DCs
/**
* Construct a factory based on a configuration array (typically from $wgLBFactoryConf)
if ( isset( $conf['readOnlyReason'] ) && is_string( $conf['readOnlyReason'] ) ) {
$this->readOnlyReason = $conf['readOnlyReason'];
}
- $this->chronProt = $this->newChronologyProtector();
- $this->trxProfiler = Profiler::instance()->getTransactionProfiler();
// Use APC/memcached style caching, but avoids loops with CACHE_DB (T141804)
- $cache = ObjectCache::getLocalServerInstance();
- if ( $cache->getQoS( $cache::ATTR_EMULATION ) > $cache::QOS_EMULATION_SQL ) {
- $this->srvCache = $cache;
+ $sCache = ObjectCache::getLocalServerInstance();
+ if ( $sCache->getQoS( $sCache::ATTR_EMULATION ) > $sCache::QOS_EMULATION_SQL ) {
+ $this->srvCache = $sCache;
} else {
$this->srvCache = new EmptyBagOStuff();
}
+ $cCache = ObjectCache::getLocalClusterInstance();
+ if ( $cCache->getQoS( $cCache::ATTR_EMULATION ) > $cCache::QOS_EMULATION_SQL ) {
+ $this->memCache = $cCache;
+ } else {
+ $this->memCache = new EmptyBagOStuff();
+ }
$wCache = ObjectCache::getMainWANInstance();
if ( $wCache->getQoS( $wCache::ATTR_EMULATION ) > $wCache::QOS_EMULATION_SQL ) {
$this->wanCache = $wCache;
} else {
$this->wanCache = WANObjectCache::newEmpty();
}
+ $this->trxProfiler = Profiler::instance()->getTransactionProfiler();
$this->trxLogger = LoggerFactory::getInstance( 'DBTransaction' );
+ $this->replLogger = LoggerFactory::getInstance( 'DBReplication' );
+ $this->chronProt = $this->newChronologyProtector();
$this->ticket = mt_rand();
}
* @see LoadBalancer::disable()
*/
public function destroy() {
- $this->shutdown();
+ $this->shutdown( self::SHUTDOWN_NO_CHRONPROT );
$this->forEachLBCallMethod( 'disable' );
}
'LBFactory_Simple' => 'LBFactorySimple',
'LBFactory_Single' => 'LBFactorySingle',
'LBFactory_Multi' => 'LBFactoryMulti',
- 'LBFactory_Fake' => 'LBFactoryFake',
];
$class = $config['class'];
* @deprecated since 1.27, use LBFactory::destroy()
*/
public static function destroyInstance() {
- self::singleton()->destroy();
+ MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->destroy();
}
/**
/**
* Prepare all tracked load balancers for shutdown
- * @param integer $flags Supports SHUTDOWN_* flags
- */
- public function shutdown( $flags = 0 ) {
- if ( !( $flags & self::SHUTDOWN_NO_CHRONPROT ) ) {
- $this->shutdownChronologyProtector( $this->chronProt );
+ * @param integer $mode One of the class SHUTDOWN_* constants
+ * @param callable|null $workCallback Work to mask ChronologyProtector writes
+ */
+ public function shutdown(
+ $mode = self::SHUTDOWN_CHRONPROT_SYNC, callable $workCallback = null
+ ) {
+ if ( $mode === self::SHUTDOWN_CHRONPROT_SYNC ) {
+ $this->shutdownChronologyProtector( $this->chronProt, $workCallback, 'sync' );
+ } elseif ( $mode === self::SHUTDOWN_CHRONPROT_ASYNC ) {
+ $this->shutdownChronologyProtector( $this->chronProt, null, 'async' );
}
+
$this->commitMasterChanges( __METHOD__ ); // sanity
}
/**
* Determine if any master connection has pending/written changes from this request
+ * @param float $age How many seconds ago is "recent" [defaults to LB lag wait timeout]
* @return bool
* @since 1.27
*/
- public function hasOrMadeRecentMasterChanges() {
+ public function hasOrMadeRecentMasterChanges( $age = null ) {
$ret = false;
- $this->forEachLB( function ( LoadBalancer $lb ) use ( &$ret ) {
- $ret = $ret || $lb->hasOrMadeRecentMasterChanges();
+ $this->forEachLB( function ( LoadBalancer $lb ) use ( $age, &$ret ) {
+ $ret = $ret || $lb->hasOrMadeRecentMasterChanges( $age );
} );
return $ret;
}
'ifWritesSince' => null
];
- foreach ( $this->replicationWaitCallbacks as $callback ) {
- $callback();
- }
-
// Figure out which clusters need to be checked
/** @var LoadBalancer[] $lbs */
$lbs = [];
$masterPositions[$i] = $lb->getMasterPos();
}
+ // Run any listener callbacks *after* getting the DB positions. The more
+ // time spent in the callbacks, the less time is spent in waitForAll().
+ foreach ( $this->replicationWaitCallbacks as $callback ) {
+ $callback();
+ }
+
$failed = [];
foreach ( $lbs as $i => $lb ) {
if ( $masterPositions[$i] ) {
}
}
+ /**
+ * @param string $dbName DB master name (e.g. "db1052")
+ * @return float|bool UNIX timestamp when client last touched the DB or false if not recent
+ * @since 1.28
+ */
+ public function getChronologyProtectorTouched( $dbName ) {
+ return $this->chronProt->getTouched( $dbName );
+ }
+
/**
* Disable the ChronologyProtector for all load balancers
*
ObjectCache::getMainStashInstance(),
[
'ip' => $request->getIP(),
- 'agent' => $request->getHeader( 'User-Agent' )
- ]
+ 'agent' => $request->getHeader( 'User-Agent' ),
+ ],
+ $request->getFloat( 'cpPosTime', $request->getCookie( 'cpPosTime', '' ) )
);
+ $chronProt->setLogger( $this->replLogger );
if ( PHP_SAPI === 'cli' ) {
$chronProt->setEnabled( false );
} elseif ( $request->getHeader( 'ChronologyProtection' ) === 'false' ) {
}
/**
+ * Get and record all of the staged DB positions into persistent memory storage
+ *
* @param ChronologyProtector $cp
+ * @param callable|null $workCallback Work to do instead of waiting on syncing positions
+ * @param string $mode One of (sync, async); whether to wait on remote datacenters
*/
- protected function shutdownChronologyProtector( ChronologyProtector $cp ) {
- // Get all the master positions needed
+ protected function shutdownChronologyProtector(
+ ChronologyProtector $cp, $workCallback, $mode
+ ) {
+ // Record all the master positions needed
$this->forEachLB( function ( LoadBalancer $lb ) use ( $cp ) {
$cp->shutdownLB( $lb );
} );
- // Write them to the stash
- $unsavedPositions = $cp->shutdown();
+ // Write them to the persistent stash. Try to do something useful by running $work
+ // while ChronologyProtector waits for the stash write to replicate to all DCs.
+ $unsavedPositions = $cp->shutdown( $workCallback, $mode );
+ if ( $unsavedPositions && $workCallback ) {
+ // Invoke callback in case it did not cache the result yet
+ $workCallback(); // work now to block for less time in waitForAll()
+ }
// If the positions failed to write to the stash, at least wait on local datacenter
// replica DBs to catch up before responding. Even if there are several DCs, this increases
// the chance that the user will see their own changes immediately afterwards. As long
} );
}
+ /**
+ * Base parameters to LoadBalancer::__construct()
+ * @return array
+ */
+ final protected function baseLoadBalancerParams() {
+ return [
+ 'localDomain' => wfWikiID(),
+ 'readOnlyReason' => $this->readOnlyReason,
+ 'srvCache' => $this->srvCache,
+ 'memCache' => $this->memCache,
+ 'wanCache' => $this->wanCache,
+ 'trxProfiler' => $this->trxProfiler,
+ 'queryLogger' => LoggerFactory::getInstance( 'DBQuery' ),
+ 'connLogger' => LoggerFactory::getInstance( 'DBConnection' ),
+ 'replLogger' => LoggerFactory::getInstance( 'DBReplication' ),
+ 'errorLogger' => [ MWExceptionHandler::class, 'logException' ],
+ 'hostname' => wfHostname()
+ ];
+ }
+
/**
* @param LoadBalancer $lb
*/
}
}
+ /**
+ * Append ?cpPosTime parameter to a URL for ChronologyProtector purposes if needed
+ *
+ * Note that unlike cookies, this works accross domains
+ *
+ * @param string $url
+ * @param float $time UNIX timestamp just before shutdown() was called
+ * @return string
+ * @since 1.28
+ */
+ public function appendPreShutdownTimeAsQuery( $url, $time ) {
+ $usedCluster = 0;
+ $this->forEachLB( function ( LoadBalancer $lb ) use ( &$usedCluster ) {
+ $usedCluster |= ( $lb->getServerCount() > 1 );
+ } );
+
+ if ( !$usedCluster ) {
+ return $url; // no master/replica clusters touched
+ }
+
+ return wfAppendQuery( $url, [ 'cpPosTime' => $time ] );
+ }
+
/**
* Close all open database connections on all open load balancers.
* @since 1.28
public function closeAll() {
$this->forEachLBCallMethod( 'closeAll', [] );
}
-
}
+++ /dev/null
-<?php
-/**
- * Generator of database load balancing objects.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Database
- */
-
-/**
- * LBFactory class that throws an error on any attempt to use it.
- * This will typically be done via wfGetDB().
- * Call LBFactory::disableBackend() to start using this, and
- * LBFactory::enableBackend() to return to normal behavior
- */
-class LBFactoryFake extends LBFactory {
- public function newMainLB( $wiki = false ) {
- throw new DBAccessError;
- }
-
- public function getMainLB( $wiki = false ) {
- throw new DBAccessError;
- }
-
- protected function newExternalLB( $cluster, $wiki = false ) {
- throw new DBAccessError;
- }
-
- public function getExternalLB( $cluster, $wiki = false ) {
- throw new DBAccessError;
- }
-
- public function forEachLB( $callback, array $params = [] ) {
- }
-}
/**
* @param array $conf
- * @throws MWException
+ * @throws InvalidArgumentException
*/
public function __construct( array $conf ) {
parent::__construct( $conf );
foreach ( $required as $key ) {
if ( !isset( $conf[$key] ) ) {
- throw new MWException( __CLASS__ . ": $key is required in configuration" );
+ throw new InvalidArgumentException( __CLASS__ . ": $key is required in configuration" );
}
$this->$key = $conf[$key];
}
$section = $this->getSectionForWiki( $wiki );
if ( !isset( $this->mainLBs[$section] ) ) {
$lb = $this->newMainLB( $wiki );
- $lb->parentInfo( [ 'id' => "main-$section" ] );
$this->chronProt->initLB( $lb );
$this->mainLBs[$section] = $lb;
}
/**
* @param string $cluster
* @param bool|string $wiki
- * @throws MWException
+ * @throws InvalidArgumentException
* @return LoadBalancer
*/
protected function newExternalLB( $cluster, $wiki = false ) {
if ( !isset( $this->externalLoads[$cluster] ) ) {
- throw new MWException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
+ throw new InvalidArgumentException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
}
$template = $this->serverTemplate;
if ( isset( $this->externalTemplateOverrides ) ) {
public function getExternalLB( $cluster, $wiki = false ) {
if ( !isset( $this->extLBs[$cluster] ) ) {
$this->extLBs[$cluster] = $this->newExternalLB( $cluster, $wiki );
- $this->extLBs[$cluster]->parentInfo( [ 'id' => "ext-$cluster" ] );
$this->chronProt->initLB( $this->extLBs[$cluster] );
}
* @return LoadBalancer
*/
private function newLoadBalancer( $template, $loads, $groupLoads, $readOnlyReason ) {
- $lb = new LoadBalancer( [
- 'servers' => $this->makeServerArray( $template, $loads, $groupLoads ),
- 'loadMonitor' => $this->loadMonitorClass,
- 'readOnlyReason' => $readOnlyReason,
- 'trxProfiler' => $this->trxProfiler,
- 'srvCache' => $this->srvCache,
- 'wanCache' => $this->wanCache
- ] );
-
+ $lb = new LoadBalancer( array_merge(
+ $this->baseLoadBalancerParams(),
+ [
+ 'servers' => $this->makeServerArray( $template, $loads, $groupLoads ),
+ 'loadMonitor' => $this->loadMonitorClass,
+ 'readOnlyReason' => $readOnlyReason
+ ]
+ ) );
$this->initLoadBalancer( $lb );
return $lb;
public function getMainLB( $wiki = false ) {
if ( !isset( $this->mainLB ) ) {
$this->mainLB = $this->newMainLB( $wiki );
- $this->mainLB->parentInfo( [ 'id' => 'main' ] );
$this->chronProt->initLB( $this->mainLB );
}
}
/**
- * @throws MWException
* @param string $cluster
* @param bool|string $wiki
* @return LoadBalancer
+ * @throws InvalidArgumentException
*/
protected function newExternalLB( $cluster, $wiki = false ) {
global $wgExternalServers;
if ( !isset( $wgExternalServers[$cluster] ) ) {
- throw new MWException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
+ throw new InvalidArgumentException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
}
return $this->newLoadBalancer( $wgExternalServers[$cluster] );
public function getExternalLB( $cluster, $wiki = false ) {
if ( !isset( $this->extLBs[$cluster] ) ) {
$this->extLBs[$cluster] = $this->newExternalLB( $cluster, $wiki );
- $this->extLBs[$cluster]->parentInfo( [ 'id' => "ext-$cluster" ] );
$this->chronProt->initLB( $this->extLBs[$cluster] );
}
}
private function newLoadBalancer( array $servers ) {
- $lb = new LoadBalancer( [
- 'servers' => $servers,
- 'loadMonitor' => $this->loadMonitorClass,
- 'readOnlyReason' => $this->readOnlyReason,
- 'trxProfiler' => $this->trxProfiler,
- 'srvCache' => $this->srvCache,
- 'wanCache' => $this->wanCache
- ] );
-
+ $lb = new LoadBalancer( array_merge(
+ $this->baseLoadBalancerParams(),
+ [
+ 'servers' => $servers,
+ 'loadMonitor' => $this->loadMonitorClass,
+ ]
+ ) );
$this->initLoadBalancer( $lb );
return $lb;
/**
* @param array $conf An associative array with one member:
- * - connection: The DatabaseBase connection object
+ * - connection: The IDatabase connection object
*/
public function __construct( array $conf ) {
parent::__construct( $conf );
- $this->lb = new LoadBalancerSingle( [
- 'readOnlyReason' => $this->readOnlyReason,
- 'trxProfiler' => $this->trxProfiler,
- 'srvCache' => $this->srvCache,
- 'wanCache' => $this->wanCache
- ] + $conf );
+ $this->lb = new LoadBalancerSingle( array_merge( $this->baseLoadBalancerParams(), $conf ) );
}
/**
* Helper class for LBFactorySingle.
*/
class LoadBalancerSingle extends LoadBalancer {
- /** @var DatabaseBase */
+ /** @var IDatabase */
private $db;
/**
* @param string $server
* @param bool $dbNameOverride
*
- * @return DatabaseBase
+ * @return IDatabase
*/
protected function reallyOpenConnection( $server, $dbNameOverride = false ) {
return $this->db;
+++ /dev/null
-<?php
-/**
- * Database load balancing.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Database
- */
-
-/**
- * Database load balancing object
- *
- * @todo document
- * @ingroup Database
- */
-class LoadBalancer {
- /** @var array[] Map of (server index => server config array) */
- private $mServers;
- /** @var array[] Map of (local/foreignUsed/foreignFree => server index => DatabaseBase array) */
- private $mConns;
- /** @var array Map of (server index => weight) */
- private $mLoads;
- /** @var array[] Map of (group => server index => weight) */
- private $mGroupLoads;
- /** @var bool Whether to disregard replica DB lag as a factor in replica DB selection */
- private $mAllowLagged;
- /** @var integer Seconds to spend waiting on replica DB lag to resolve */
- private $mWaitTimeout;
- /** @var array LBFactory information */
- private $mParentInfo;
-
- /** @var string The LoadMonitor subclass name */
- private $mLoadMonitorClass;
- /** @var LoadMonitor */
- private $mLoadMonitor;
- /** @var BagOStuff */
- private $srvCache;
- /** @var WANObjectCache */
- private $wanCache;
- /** @var TransactionProfiler */
- protected $trxProfiler;
-
- /** @var bool|DatabaseBase Database connection that caused a problem */
- private $mErrorConnection;
- /** @var integer The generic (not query grouped) replica DB index (of $mServers) */
- private $mReadIndex;
- /** @var bool|DBMasterPos False if not set */
- private $mWaitForPos;
- /** @var bool Whether the generic reader fell back to a lagged replica DB */
- private $laggedReplicaMode = false;
- /** @var bool Whether the generic reader fell back to a lagged replica DB */
- private $allReplicasDownMode = false;
- /** @var string The last DB selection or connection error */
- private $mLastError = 'Unknown error';
- /** @var string|bool Reason the LB is read-only or false if not */
- private $readOnlyReason = false;
- /** @var integer Total connections opened */
- private $connsOpened = 0;
- /** @var string|bool String if a requested DBO_TRX transaction round is active */
- private $trxRoundId = false;
- /** @var array[] Map of (name => callable) */
- private $trxRecurringCallbacks = [];
-
- /** @var integer Warn when this many connection are held */
- const CONN_HELD_WARN_THRESHOLD = 10;
- /** @var integer Default 'max lag' when unspecified */
- const MAX_LAG_DEFAULT = 10;
- /** @var integer Max time to wait for a replica DB to catch up (e.g. ChronologyProtector) */
- const POS_WAIT_TIMEOUT = 10;
- /** @var integer Seconds to cache master server read-only status */
- const TTL_CACHE_READONLY = 5;
-
- /**
- * @var boolean
- */
- private $disabled = false;
-
- /**
- * @param array $params Array with keys:
- * - servers : Required. Array of server info structures.
- * - loadMonitor : Name of a class used to fetch server lag and load.
- * - readOnlyReason : Reason the master DB is read-only if so [optional]
- * - waitTimeout : Maximum time to wait for replicas for consistency [optional]
- * - srvCache : BagOStuff object [optional]
- * - wanCache : WANObjectCache object [optional]
- * @throws MWException
- */
- public function __construct( array $params ) {
- if ( !isset( $params['servers'] ) ) {
- throw new MWException( __CLASS__ . ': missing servers parameter' );
- }
- $this->mServers = $params['servers'];
- $this->mWaitTimeout = isset( $params['waitTimeout'] )
- ? $params['waitTimeout']
- : self::POS_WAIT_TIMEOUT;
-
- $this->mReadIndex = -1;
- $this->mWriteIndex = -1;
- $this->mConns = [
- 'local' => [],
- 'foreignUsed' => [],
- 'foreignFree' => [] ];
- $this->mLoads = [];
- $this->mWaitForPos = false;
- $this->mErrorConnection = false;
- $this->mAllowLagged = false;
-
- if ( isset( $params['readOnlyReason'] ) && is_string( $params['readOnlyReason'] ) ) {
- $this->readOnlyReason = $params['readOnlyReason'];
- }
-
- if ( isset( $params['loadMonitor'] ) ) {
- $this->mLoadMonitorClass = $params['loadMonitor'];
- } else {
- $master = reset( $params['servers'] );
- if ( isset( $master['type'] ) && $master['type'] === 'mysql' ) {
- $this->mLoadMonitorClass = 'LoadMonitorMySQL';
- } else {
- $this->mLoadMonitorClass = 'LoadMonitorNull';
- }
- }
-
- foreach ( $params['servers'] as $i => $server ) {
- $this->mLoads[$i] = $server['load'];
- if ( isset( $server['groupLoads'] ) ) {
- foreach ( $server['groupLoads'] as $group => $ratio ) {
- if ( !isset( $this->mGroupLoads[$group] ) ) {
- $this->mGroupLoads[$group] = [];
- }
- $this->mGroupLoads[$group][$i] = $ratio;
- }
- }
- }
-
- if ( isset( $params['srvCache'] ) ) {
- $this->srvCache = $params['srvCache'];
- } else {
- $this->srvCache = new EmptyBagOStuff();
- }
- if ( isset( $params['wanCache'] ) ) {
- $this->wanCache = $params['wanCache'];
- } else {
- $this->wanCache = WANObjectCache::newEmpty();
- }
- if ( isset( $params['trxProfiler'] ) ) {
- $this->trxProfiler = $params['trxProfiler'];
- } else {
- $this->trxProfiler = new TransactionProfiler();
- }
- }
-
- /**
- * Get a LoadMonitor instance
- *
- * @return LoadMonitor
- */
- private function getLoadMonitor() {
- if ( !isset( $this->mLoadMonitor ) ) {
- $class = $this->mLoadMonitorClass;
- $this->mLoadMonitor = new $class( $this );
- }
-
- return $this->mLoadMonitor;
- }
-
- /**
- * Get or set arbitrary data used by the parent object, usually an LBFactory
- * @param mixed $x
- * @return mixed
- */
- public function parentInfo( $x = null ) {
- return wfSetVar( $this->mParentInfo, $x );
- }
-
- /**
- * @param array $loads
- * @param bool|string $wiki Wiki to get non-lagged for
- * @param int $maxLag Restrict the maximum allowed lag to this many seconds
- * @return bool|int|string
- */
- private function getRandomNonLagged( array $loads, $wiki = false, $maxLag = INF ) {
- $lags = $this->getLagTimes( $wiki );
-
- # Unset excessively lagged servers
- foreach ( $lags as $i => $lag ) {
- if ( $i != 0 ) {
- # How much lag this server nominally is allowed to have
- $maxServerLag = isset( $this->mServers[$i]['max lag'] )
- ? $this->mServers[$i]['max lag']
- : self::MAX_LAG_DEFAULT; // default
- # Constrain that futher by $maxLag argument
- $maxServerLag = min( $maxServerLag, $maxLag );
-
- $host = $this->getServerName( $i );
- if ( $lag === false && !is_infinite( $maxServerLag ) ) {
- wfDebugLog( 'replication', "Server $host (#$i) is not replicating?" );
- unset( $loads[$i] );
- } elseif ( $lag > $maxServerLag ) {
- wfDebugLog( 'replication', "Server $host (#$i) has >= $lag seconds of lag" );
- unset( $loads[$i] );
- }
- }
- }
-
- # Find out if all the replica DBs with non-zero load are lagged
- $sum = 0;
- foreach ( $loads as $load ) {
- $sum += $load;
- }
- if ( $sum == 0 ) {
- # No appropriate DB servers except maybe the master and some replica DBs with zero load
- # Do NOT use the master
- # Instead, this function will return false, triggering read-only mode,
- # and a lagged replica DB will be used instead.
- return false;
- }
-
- if ( count( $loads ) == 0 ) {
- return false;
- }
-
- # Return a random representative of the remainder
- return ArrayUtils::pickRandom( $loads );
- }
-
- /**
- * Get the index of the reader connection, which may be a replica DB
- * This takes into account load ratios and lag times. It should
- * always return a consistent index during a given invocation
- *
- * Side effect: opens connections to databases
- * @param string|bool $group Query group, or false for the generic reader
- * @param string|bool $wiki Wiki ID, or false for the current wiki
- * @throws MWException
- * @return bool|int|string
- */
- public function getReaderIndex( $group = false, $wiki = false ) {
- global $wgDBtype;
-
- # @todo FIXME: For now, only go through all this for mysql databases
- if ( $wgDBtype != 'mysql' ) {
- return $this->getWriterIndex();
- }
-
- if ( count( $this->mServers ) == 1 ) {
- # Skip the load balancing if there's only one server
- return 0;
- } elseif ( $group === false && $this->mReadIndex >= 0 ) {
- # Shortcut if generic reader exists already
- return $this->mReadIndex;
- }
-
- # Find the relevant load array
- if ( $group !== false ) {
- if ( isset( $this->mGroupLoads[$group] ) ) {
- $nonErrorLoads = $this->mGroupLoads[$group];
- } else {
- # No loads for this group, return false and the caller can use some other group
- wfDebugLog( 'connect', __METHOD__ . ": no loads for group $group\n" );
-
- return false;
- }
- } else {
- $nonErrorLoads = $this->mLoads;
- }
-
- if ( !count( $nonErrorLoads ) ) {
- throw new MWException( "Empty server array given to LoadBalancer" );
- }
-
- # Scale the configured load ratios according to the dynamic load (if the load monitor supports it)
- $this->getLoadMonitor()->scaleLoads( $nonErrorLoads, $group, $wiki );
-
- $laggedReplicaMode = false;
-
- # No server found yet
- $i = false;
- # First try quickly looking through the available servers for a server that
- # meets our criteria
- $currentLoads = $nonErrorLoads;
- while ( count( $currentLoads ) ) {
- if ( $this->mAllowLagged || $laggedReplicaMode ) {
- $i = ArrayUtils::pickRandom( $currentLoads );
- } else {
- $i = false;
- if ( $this->mWaitForPos && $this->mWaitForPos->asOfTime() ) {
- # ChronologyProtecter causes mWaitForPos to be set via sessions.
- # This triggers doWait() after connect, so it's especially good to
- # avoid lagged servers so as to avoid just blocking in that method.
- $ago = microtime( true ) - $this->mWaitForPos->asOfTime();
- # Aim for <= 1 second of waiting (being too picky can backfire)
- $i = $this->getRandomNonLagged( $currentLoads, $wiki, $ago + 1 );
- }
- if ( $i === false ) {
- # Any server with less lag than it's 'max lag' param is preferable
- $i = $this->getRandomNonLagged( $currentLoads, $wiki );
- }
- if ( $i === false && count( $currentLoads ) != 0 ) {
- # All replica DBs lagged. Switch to read-only mode
- wfDebugLog( 'replication', "All replica DBs lagged. Switch to read-only mode" );
- $i = ArrayUtils::pickRandom( $currentLoads );
- $laggedReplicaMode = true;
- }
- }
-
- if ( $i === false ) {
- # pickRandom() returned false
- # This is permanent and means the configuration or the load monitor
- # wants us to return false.
- wfDebugLog( 'connect', __METHOD__ . ": pickRandom() returned false" );
-
- return false;
- }
-
- $serverName = $this->getServerName( $i );
- wfDebugLog( 'connect', __METHOD__ . ": Using reader #$i: $serverName..." );
-
- $conn = $this->openConnection( $i, $wiki );
- if ( !$conn ) {
- wfDebugLog( 'connect', __METHOD__ . ": Failed connecting to $i/$wiki" );
- unset( $nonErrorLoads[$i] );
- unset( $currentLoads[$i] );
- $i = false;
- continue;
- }
-
- // Decrement reference counter, we are finished with this connection.
- // It will be incremented for the caller later.
- if ( $wiki !== false ) {
- $this->reuseConnection( $conn );
- }
-
- # Return this server
- break;
- }
-
- # If all servers were down, quit now
- if ( !count( $nonErrorLoads ) ) {
- wfDebugLog( 'connect', "All servers down" );
- }
-
- if ( $i !== false ) {
- # Replica DB connection successful.
- # Wait for the session master pos for a short time.
- if ( $this->mWaitForPos && $i > 0 ) {
- $this->doWait( $i );
- }
- if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $group === false ) {
- $this->mReadIndex = $i;
- # Record if the generic reader index is in "lagged replica DB" mode
- if ( $laggedReplicaMode ) {
- $this->laggedReplicaMode = true;
- }
- }
- $serverName = $this->getServerName( $i );
- wfDebugLog( 'connect', __METHOD__ .
- ": using server $serverName for group '$group'\n" );
- }
-
- return $i;
- }
-
- /**
- * Set the master wait position
- * If a DB_REPLICA connection has been opened already, waits
- * Otherwise sets a variable telling it to wait if such a connection is opened
- * @param DBMasterPos $pos
- */
- public function waitFor( $pos ) {
- $this->mWaitForPos = $pos;
- $i = $this->mReadIndex;
-
- if ( $i > 0 ) {
- if ( !$this->doWait( $i ) ) {
- $this->laggedReplicaMode = true;
- }
- }
- }
-
- /**
- * Set the master wait position and wait for a "generic" replica DB to catch up to it
- *
- * This can be used a faster proxy for waitForAll()
- *
- * @param DBMasterPos $pos
- * @param int $timeout Max seconds to wait; default is mWaitTimeout
- * @return bool Success (able to connect and no timeouts reached)
- * @since 1.26
- */
- public function waitForOne( $pos, $timeout = null ) {
- $this->mWaitForPos = $pos;
-
- $i = $this->mReadIndex;
- if ( $i <= 0 ) {
- // Pick a generic replica DB if there isn't one yet
- $readLoads = $this->mLoads;
- unset( $readLoads[$this->getWriterIndex()] ); // replica DBs only
- $readLoads = array_filter( $readLoads ); // with non-zero load
- $i = ArrayUtils::pickRandom( $readLoads );
- }
-
- if ( $i > 0 ) {
- $ok = $this->doWait( $i, true, $timeout );
- } else {
- $ok = true; // no applicable loads
- }
-
- return $ok;
- }
-
- /**
- * Set the master wait position and wait for ALL replica DBs to catch up to it
- * @param DBMasterPos $pos
- * @param int $timeout Max seconds to wait; default is mWaitTimeout
- * @return bool Success (able to connect and no timeouts reached)
- */
- public function waitForAll( $pos, $timeout = null ) {
- $this->mWaitForPos = $pos;
- $serverCount = count( $this->mServers );
-
- $ok = true;
- for ( $i = 1; $i < $serverCount; $i++ ) {
- if ( $this->mLoads[$i] > 0 ) {
- $ok = $this->doWait( $i, true, $timeout ) && $ok;
- }
- }
-
- return $ok;
- }
-
- /**
- * Get any open connection to a given server index, local or foreign
- * Returns false if there is no connection open
- *
- * @param int $i
- * @return DatabaseBase|bool False on failure
- */
- public function getAnyOpenConnection( $i ) {
- foreach ( $this->mConns as $conns ) {
- if ( !empty( $conns[$i] ) ) {
- return reset( $conns[$i] );
- }
- }
-
- return false;
- }
-
- /**
- * Wait for a given replica DB to catch up to the master pos stored in $this
- * @param int $index Server index
- * @param bool $open Check the server even if a new connection has to be made
- * @param int $timeout Max seconds to wait; default is mWaitTimeout
- * @return bool
- */
- protected function doWait( $index, $open = false, $timeout = null ) {
- $close = false; // close the connection afterwards
-
- // Check if we already know that the DB has reached this point
- $server = $this->getServerName( $index );
- $key = $this->srvCache->makeGlobalKey( __CLASS__, 'last-known-pos', $server );
- /** @var DBMasterPos $knownReachedPos */
- $knownReachedPos = $this->srvCache->get( $key );
- if ( $knownReachedPos && $knownReachedPos->hasReached( $this->mWaitForPos ) ) {
- wfDebugLog( 'replication', __METHOD__ .
- ": replica DB $server known to be caught up (pos >= $knownReachedPos).\n" );
- return true;
- }
-
- // Find a connection to wait on, creating one if needed and allowed
- $conn = $this->getAnyOpenConnection( $index );
- if ( !$conn ) {
- if ( !$open ) {
- wfDebugLog( 'replication', __METHOD__ . ": no connection open for $server\n" );
-
- return false;
- } else {
- $conn = $this->openConnection( $index, '' );
- if ( !$conn ) {
- wfDebugLog( 'replication', __METHOD__ . ": failed to connect to $server\n" );
-
- return false;
- }
- // Avoid connection spam in waitForAll() when connections
- // are made just for the sake of doing this lag check.
- $close = true;
- }
- }
-
- wfDebugLog( 'replication', __METHOD__ . ": Waiting for replica DB $server to catch up...\n" );
- $timeout = $timeout ?: $this->mWaitTimeout;
- $result = $conn->masterPosWait( $this->mWaitForPos, $timeout );
-
- if ( $result == -1 || is_null( $result ) ) {
- // Timed out waiting for replica DB, use master instead
- $msg = __METHOD__ . ": Timed out waiting on $server pos {$this->mWaitForPos}";
- wfDebugLog( 'replication', "$msg\n" );
- wfDebugLog( 'DBPerformance', "$msg:\n" . wfBacktrace( true ) );
- $ok = false;
- } else {
- wfDebugLog( 'replication', __METHOD__ . ": Done\n" );
- $ok = true;
- // Remember that the DB reached this point
- $this->srvCache->set( $key, $this->mWaitForPos, BagOStuff::TTL_DAY );
- }
-
- if ( $close ) {
- $this->closeConnection( $conn );
- }
-
- return $ok;
- }
-
- /**
- * Get a connection by index
- * This is the main entry point for this class.
- *
- * @param int $i Server index
- * @param array|string|bool $groups Query group(s), or false for the generic reader
- * @param string|bool $wiki Wiki ID, or false for the current wiki
- *
- * @throws MWException
- * @return DatabaseBase
- */
- public function getConnection( $i, $groups = [], $wiki = false ) {
- if ( $i === null || $i === false ) {
- throw new MWException( 'Attempt to call ' . __METHOD__ .
- ' with invalid server index' );
- }
-
- if ( $wiki === wfWikiID() ) {
- $wiki = false;
- }
-
- $groups = ( $groups === false || $groups === [] )
- ? [ false ] // check one "group": the generic pool
- : (array)$groups;
-
- $masterOnly = ( $i == DB_MASTER || $i == $this->getWriterIndex() );
- $oldConnsOpened = $this->connsOpened; // connections open now
-
- if ( $i == DB_MASTER ) {
- $i = $this->getWriterIndex();
- } else {
- # Try to find an available server in any the query groups (in order)
- foreach ( $groups as $group ) {
- $groupIndex = $this->getReaderIndex( $group, $wiki );
- if ( $groupIndex !== false ) {
- $i = $groupIndex;
- break;
- }
- }
- }
-
- # Operation-based index
- if ( $i == DB_REPLICA ) {
- $this->mLastError = 'Unknown error'; // reset error string
- # Try the general server pool if $groups are unavailable.
- $i = in_array( false, $groups, true )
- ? false // don't bother with this if that is what was tried above
- : $this->getReaderIndex( false, $wiki );
- # Couldn't find a working server in getReaderIndex()?
- if ( $i === false ) {
- $this->mLastError = 'No working replica DB server: ' . $this->mLastError;
-
- return $this->reportConnectionError();
- }
- }
-
- # Now we have an explicit index into the servers array
- $conn = $this->openConnection( $i, $wiki );
- if ( !$conn ) {
- return $this->reportConnectionError();
- }
-
- # Profile any new connections that happen
- if ( $this->connsOpened > $oldConnsOpened ) {
- $host = $conn->getServer();
- $dbname = $conn->getDBname();
- $trxProf = Profiler::instance()->getTransactionProfiler();
- $trxProf->recordConnection( $host, $dbname, $masterOnly );
- }
-
- if ( $masterOnly ) {
- # Make master-requested DB handles inherit any read-only mode setting
- $conn->setLBInfo( 'readOnlyReason', $this->getReadOnlyReason( $wiki, $conn ) );
- }
-
- return $conn;
- }
-
- /**
- * Mark a foreign connection as being available for reuse under a different
- * DB name or prefix. This mechanism is reference-counted, and must be called
- * the same number of times as getConnection() to work.
- *
- * @param DatabaseBase $conn
- * @throws MWException
- */
- public function reuseConnection( $conn ) {
- $serverIndex = $conn->getLBInfo( 'serverIndex' );
- $refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
- if ( $serverIndex === null || $refCount === null ) {
- /**
- * This can happen in code like:
- * foreach ( $dbs as $db ) {
- * $conn = $lb->getConnection( DB_REPLICA, [], $db );
- * ...
- * $lb->reuseConnection( $conn );
- * }
- * When a connection to the local DB is opened in this way, reuseConnection()
- * should be ignored
- */
- return;
- }
-
- $dbName = $conn->getDBname();
- $prefix = $conn->tablePrefix();
- if ( strval( $prefix ) !== '' ) {
- $wiki = "$dbName-$prefix";
- } else {
- $wiki = $dbName;
- }
- if ( $this->mConns['foreignUsed'][$serverIndex][$wiki] !== $conn ) {
- throw new MWException( __METHOD__ . ": connection not found, has " .
- "the connection been freed already?" );
- }
- $conn->setLBInfo( 'foreignPoolRefCount', --$refCount );
- if ( $refCount <= 0 ) {
- $this->mConns['foreignFree'][$serverIndex][$wiki] = $conn;
- unset( $this->mConns['foreignUsed'][$serverIndex][$wiki] );
- wfDebug( __METHOD__ . ": freed connection $serverIndex/$wiki\n" );
- } else {
- wfDebug( __METHOD__ . ": reference count for $serverIndex/$wiki reduced to $refCount\n" );
- }
- }
-
- /**
- * Get a database connection handle reference
- *
- * The handle's methods wrap simply wrap those of a DatabaseBase handle
- *
- * @see LoadBalancer::getConnection() for parameter information
- *
- * @param int $db
- * @param array|string|bool $groups Query group(s), or false for the generic reader
- * @param string|bool $wiki Wiki ID, or false for the current wiki
- * @return DBConnRef
- */
- public function getConnectionRef( $db, $groups = [], $wiki = false ) {
- return new DBConnRef( $this, $this->getConnection( $db, $groups, $wiki ) );
- }
-
- /**
- * Get a database connection handle reference without connecting yet
- *
- * The handle's methods wrap simply wrap those of a DatabaseBase handle
- *
- * @see LoadBalancer::getConnection() for parameter information
- *
- * @param int $db
- * @param array|string|bool $groups Query group(s), or false for the generic reader
- * @param string|bool $wiki Wiki ID, or false for the current wiki
- * @return DBConnRef
- */
- public function getLazyConnectionRef( $db, $groups = [], $wiki = false ) {
- return new DBConnRef( $this, [ $db, $groups, $wiki ] );
- }
-
- /**
- * Open a connection to the server given by the specified index
- * Index must be an actual index into the array.
- * If the server is already open, returns it.
- *
- * On error, returns false, and the connection which caused the
- * error will be available via $this->mErrorConnection.
- *
- * @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError.
- *
- * @param int $i Server index
- * @param string|bool $wiki Wiki ID, or false for the current wiki
- * @return DatabaseBase|bool Returns false on errors
- */
- public function openConnection( $i, $wiki = false ) {
- if ( $wiki !== false ) {
- $conn = $this->openForeignConnection( $i, $wiki );
- } elseif ( isset( $this->mConns['local'][$i][0] ) ) {
- $conn = $this->mConns['local'][$i][0];
- } else {
- $server = $this->mServers[$i];
- $server['serverIndex'] = $i;
- $conn = $this->reallyOpenConnection( $server, false );
- $serverName = $this->getServerName( $i );
- if ( $conn->isOpen() ) {
- wfDebugLog( 'connect', "Connected to database $i at $serverName\n" );
- $this->mConns['local'][$i][0] = $conn;
- } else {
- wfDebugLog( 'connect', "Failed to connect to database $i at $serverName\n" );
- $this->mErrorConnection = $conn;
- $conn = false;
- }
- }
-
- if ( $conn && !$conn->isOpen() ) {
- // Connection was made but later unrecoverably lost for some reason.
- // Do not return a handle that will just throw exceptions on use,
- // but let the calling code (e.g. getReaderIndex) try another server.
- // See DatabaseMyslBase::ping() for how this can happen.
- $this->mErrorConnection = $conn;
- $conn = false;
- }
-
- return $conn;
- }
-
- /**
- * Open a connection to a foreign DB, or return one if it is already open.
- *
- * Increments a reference count on the returned connection which locks the
- * connection to the requested wiki. This reference count can be
- * decremented by calling reuseConnection().
- *
- * If a connection is open to the appropriate server already, but with the wrong
- * database, it will be switched to the right database and returned, as long as
- * it has been freed first with reuseConnection().
- *
- * On error, returns false, and the connection which caused the
- * error will be available via $this->mErrorConnection.
- *
- * @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError.
- *
- * @param int $i Server index
- * @param string $wiki Wiki ID to open
- * @return DatabaseBase
- */
- private function openForeignConnection( $i, $wiki ) {
- list( $dbName, $prefix ) = wfSplitWikiID( $wiki );
- if ( isset( $this->mConns['foreignUsed'][$i][$wiki] ) ) {
- // Reuse an already-used connection
- $conn = $this->mConns['foreignUsed'][$i][$wiki];
- wfDebug( __METHOD__ . ": reusing connection $i/$wiki\n" );
- } elseif ( isset( $this->mConns['foreignFree'][$i][$wiki] ) ) {
- // Reuse a free connection for the same wiki
- $conn = $this->mConns['foreignFree'][$i][$wiki];
- unset( $this->mConns['foreignFree'][$i][$wiki] );
- $this->mConns['foreignUsed'][$i][$wiki] = $conn;
- wfDebug( __METHOD__ . ": reusing free connection $i/$wiki\n" );
- } elseif ( !empty( $this->mConns['foreignFree'][$i] ) ) {
- // Reuse a connection from another wiki
- $conn = reset( $this->mConns['foreignFree'][$i] );
- $oldWiki = key( $this->mConns['foreignFree'][$i] );
-
- // The empty string as a DB name means "don't care".
- // DatabaseMysqlBase::open() already handle this on connection.
- if ( $dbName !== '' && !$conn->selectDB( $dbName ) ) {
- $this->mLastError = "Error selecting database $dbName on server " .
- $conn->getServer() . " from client host " . wfHostname() . "\n";
- $this->mErrorConnection = $conn;
- $conn = false;
- } else {
- $conn->tablePrefix( $prefix );
- unset( $this->mConns['foreignFree'][$i][$oldWiki] );
- $this->mConns['foreignUsed'][$i][$wiki] = $conn;
- wfDebug( __METHOD__ . ": reusing free connection from $oldWiki for $wiki\n" );
- }
- } else {
- // Open a new connection
- $server = $this->mServers[$i];
- $server['serverIndex'] = $i;
- $server['foreignPoolRefCount'] = 0;
- $server['foreign'] = true;
- $conn = $this->reallyOpenConnection( $server, $dbName );
- if ( !$conn->isOpen() ) {
- wfDebug( __METHOD__ . ": error opening connection for $i/$wiki\n" );
- $this->mErrorConnection = $conn;
- $conn = false;
- } else {
- $conn->tablePrefix( $prefix );
- $this->mConns['foreignUsed'][$i][$wiki] = $conn;
- wfDebug( __METHOD__ . ": opened new connection for $i/$wiki\n" );
- }
- }
-
- // Increment reference count
- if ( $conn ) {
- $refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
- $conn->setLBInfo( 'foreignPoolRefCount', $refCount + 1 );
- }
-
- return $conn;
- }
-
- /**
- * Test if the specified index represents an open connection
- *
- * @param int $index Server index
- * @access private
- * @return bool
- */
- private function isOpen( $index ) {
- if ( !is_integer( $index ) ) {
- return false;
- }
-
- return (bool)$this->getAnyOpenConnection( $index );
- }
-
- /**
- * Really opens a connection. Uncached.
- * Returns a Database object whether or not the connection was successful.
- * @access private
- *
- * @param array $server
- * @param bool $dbNameOverride
- * @throws MWException
- * @return DatabaseBase
- */
- protected function reallyOpenConnection( $server, $dbNameOverride = false ) {
- if ( $this->disabled ) {
- throw new DBAccessError();
- }
-
- if ( !is_array( $server ) ) {
- throw new MWException( 'You must update your load-balancing configuration. ' .
- 'See DefaultSettings.php entry for $wgDBservers.' );
- }
-
- if ( $dbNameOverride !== false ) {
- $server['dbname'] = $dbNameOverride;
- }
-
- // Let the handle know what the cluster master is (e.g. "db1052")
- $masterName = $this->getServerName( 0 );
- $server['clusterMasterHost'] = $masterName;
-
- // Log when many connection are made on requests
- if ( ++$this->connsOpened >= self::CONN_HELD_WARN_THRESHOLD ) {
- wfDebugLog( 'DBPerformance', __METHOD__ . ": " .
- "{$this->connsOpened}+ connections made (master=$masterName)\n" .
- wfBacktrace( true ) );
- }
-
- # Create object
- try {
- $db = DatabaseBase::factory( $server['type'], $server );
- } catch ( DBConnectionError $e ) {
- // FIXME: This is probably the ugliest thing I have ever done to
- // PHP. I'm half-expecting it to segfault, just out of disgust. -- TS
- $db = $e->db;
- }
-
- $db->setLBInfo( $server );
- $db->setLazyMasterHandle(
- $this->getLazyConnectionRef( DB_MASTER, [], $db->getWikiID() )
- );
- $db->setTransactionProfiler( $this->trxProfiler );
-
- if ( $server['serverIndex'] === $this->getWriterIndex() ) {
- if ( $this->trxRoundId !== false ) {
- $this->applyTransactionRoundFlags( $db );
- }
- foreach ( $this->trxRecurringCallbacks as $name => $callback ) {
- $db->setTransactionListener( $name, $callback );
- }
- }
-
- return $db;
- }
-
- /**
- * @throws DBConnectionError
- * @return bool
- */
- private function reportConnectionError() {
- $conn = $this->mErrorConnection; // The connection which caused the error
- $context = [
- 'method' => __METHOD__,
- 'last_error' => $this->mLastError,
- ];
-
- if ( !is_object( $conn ) ) {
- // No last connection, probably due to all servers being too busy
- wfLogDBError(
- "LB failure with no last connection. Connection error: {last_error}",
- $context
- );
-
- // If all servers were busy, mLastError will contain something sensible
- throw new DBConnectionError( null, $this->mLastError );
- } else {
- $context['db_server'] = $conn->getProperty( 'mServer' );
- wfLogDBError(
- "Connection error: {last_error} ({db_server})",
- $context
- );
-
- // throws DBConnectionError
- $conn->reportConnectionError( "{$this->mLastError} ({$context['db_server']})" );
- }
-
- return false; /* not reached */
- }
-
- /**
- * @return int
- * @since 1.26
- */
- public function getWriterIndex() {
- return 0;
- }
-
- /**
- * Returns true if the specified index is a valid server index
- *
- * @param string $i
- * @return bool
- */
- public function haveIndex( $i ) {
- return array_key_exists( $i, $this->mServers );
- }
-
- /**
- * Returns true if the specified index is valid and has non-zero load
- *
- * @param string $i
- * @return bool
- */
- public function isNonZeroLoad( $i ) {
- return array_key_exists( $i, $this->mServers ) && $this->mLoads[$i] != 0;
- }
-
- /**
- * Get the number of defined servers (not the number of open connections)
- *
- * @return int
- */
- public function getServerCount() {
- return count( $this->mServers );
- }
-
- /**
- * Get the host name or IP address of the server with the specified index
- * Prefer a readable name if available.
- * @param string $i
- * @return string
- */
- public function getServerName( $i ) {
- if ( isset( $this->mServers[$i]['hostName'] ) ) {
- $name = $this->mServers[$i]['hostName'];
- } elseif ( isset( $this->mServers[$i]['host'] ) ) {
- $name = $this->mServers[$i]['host'];
- } else {
- $name = '';
- }
-
- return ( $name != '' ) ? $name : 'localhost';
- }
-
- /**
- * Return the server info structure for a given index, or false if the index is invalid.
- * @param int $i
- * @return array|bool
- */
- public function getServerInfo( $i ) {
- if ( isset( $this->mServers[$i] ) ) {
- return $this->mServers[$i];
- } else {
- return false;
- }
- }
-
- /**
- * Sets the server info structure for the given index. Entry at index $i
- * is created if it doesn't exist
- * @param int $i
- * @param array $serverInfo
- */
- public function setServerInfo( $i, array $serverInfo ) {
- $this->mServers[$i] = $serverInfo;
- }
-
- /**
- * Get the current master position for chronology control purposes
- * @return mixed
- */
- public function getMasterPos() {
- # If this entire request was served from a replica DB without opening a connection to the
- # master (however unlikely that may be), then we can fetch the position from the replica DB.
- $masterConn = $this->getAnyOpenConnection( 0 );
- if ( !$masterConn ) {
- $serverCount = count( $this->mServers );
- for ( $i = 1; $i < $serverCount; $i++ ) {
- $conn = $this->getAnyOpenConnection( $i );
- if ( $conn ) {
- return $conn->getSlavePos();
- }
- }
- } else {
- return $masterConn->getMasterPos();
- }
-
- return false;
- }
-
- /**
- * Disable this load balancer. All connections are closed, and any attempt to
- * open a new connection will result in a DBAccessError.
- *
- * @since 1.27
- */
- public function disable() {
- $this->closeAll();
- $this->disabled = true;
- }
-
- /**
- * Close all open connections
- */
- public function closeAll() {
- $this->forEachOpenConnection( function ( DatabaseBase $conn ) {
- $conn->close();
- } );
-
- $this->mConns = [
- 'local' => [],
- 'foreignFree' => [],
- 'foreignUsed' => [],
- ];
- $this->connsOpened = 0;
- }
-
- /**
- * Close a connection
- * Using this function makes sure the LoadBalancer knows the connection is closed.
- * If you use $conn->close() directly, the load balancer won't update its state.
- * @param DatabaseBase $conn
- */
- public function closeConnection( $conn ) {
- $done = false;
- foreach ( $this->mConns as $i1 => $conns2 ) {
- foreach ( $conns2 as $i2 => $conns3 ) {
- foreach ( $conns3 as $i3 => $candidateConn ) {
- if ( $conn === $candidateConn ) {
- $conn->close();
- unset( $this->mConns[$i1][$i2][$i3] );
- --$this->connsOpened;
- $done = true;
- break;
- }
- }
- }
- }
- if ( !$done ) {
- $conn->close();
- }
- }
-
- /**
- * Commit transactions on all open connections
- * @param string $fname Caller name
- * @throws DBExpectedError
- */
- public function commitAll( $fname = __METHOD__ ) {
- $failures = [];
-
- $restore = ( $this->trxRoundId !== false );
- $this->trxRoundId = false;
- $this->forEachOpenConnection(
- function ( DatabaseBase $conn ) use ( $fname, $restore, &$failures ) {
- try {
- $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
- } catch ( DBError $e ) {
- MWExceptionHandler::logException( $e );
- $failures[] = "{$conn->getServer()}: {$e->getMessage()}";
- }
- if ( $restore && $conn->getLBInfo( 'master' ) ) {
- $this->undoTransactionRoundFlags( $conn );
- }
- }
- );
-
- if ( $failures ) {
- throw new DBExpectedError(
- null,
- "Commit failed on server(s) " . implode( "\n", array_unique( $failures ) )
- );
- }
- }
-
- /**
- * Perform all pre-commit callbacks that remain part of the atomic transactions
- * and disable any post-commit callbacks until runMasterPostTrxCallbacks()
- * @since 1.28
- */
- public function finalizeMasterChanges() {
- $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) {
- // Any error should cause all DB transactions to be rolled back together
- $conn->setTrxEndCallbackSuppression( false );
- $conn->runOnTransactionPreCommitCallbacks();
- // Defer post-commit callbacks until COMMIT finishes for all DBs
- $conn->setTrxEndCallbackSuppression( true );
- } );
- }
-
- /**
- * Perform all pre-commit checks for things like replication safety
- * @param array $options Includes:
- * - maxWriteDuration : max write query duration time in seconds
- * @throws DBTransactionError
- * @since 1.28
- */
- public function approveMasterChanges( array $options ) {
- $limit = isset( $options['maxWriteDuration'] ) ? $options['maxWriteDuration'] : 0;
- $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) use ( $limit ) {
- // If atomic sections or explicit transactions are still open, some caller must have
- // caught an exception but failed to properly rollback any changes. Detect that and
- // throw and error (causing rollback).
- if ( $conn->explicitTrxActive() ) {
- throw new DBTransactionError(
- $conn,
- "Explicit transaction still active. A caller may have caught an error."
- );
- }
- // Assert that the time to replicate the transaction will be sane.
- // If this fails, then all DB transactions will be rollback back together.
- $time = $conn->pendingWriteQueryDuration( $conn::ESTIMATE_DB_APPLY );
- if ( $limit > 0 && $time > $limit ) {
- throw new DBTransactionError(
- $conn,
- wfMessage( 'transaction-duration-limit-exceeded', $time, $limit )->text()
- );
- }
- // If a connection sits idle while slow queries execute on another, that connection
- // may end up dropped before the commit round is reached. Ping servers to detect this.
- if ( $conn->writesOrCallbacksPending() && !$conn->ping() ) {
- throw new DBTransactionError(
- $conn,
- "A connection to the {$conn->getDBname()} database was lost before commit."
- );
- }
- } );
- }
-
- /**
- * Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
- *
- * The DBO_TRX setting will be reverted to the default in each of these methods:
- * - commitMasterChanges()
- * - rollbackMasterChanges()
- * - commitAll()
- * This allows for custom transaction rounds from any outer transaction scope.
- *
- * @param string $fname
- * @throws DBExpectedError
- * @since 1.28
- */
- public function beginMasterChanges( $fname = __METHOD__ ) {
- if ( $this->trxRoundId !== false ) {
- throw new DBTransactionError(
- null,
- "$fname: Transaction round '{$this->trxRoundId}' already started."
- );
- }
- $this->trxRoundId = $fname;
-
- $failures = [];
- $this->forEachOpenMasterConnection(
- function ( DatabaseBase $conn ) use ( $fname, &$failures ) {
- $conn->setTrxEndCallbackSuppression( true );
- try {
- $conn->flushSnapshot( $fname );
- } catch ( DBError $e ) {
- MWExceptionHandler::logException( $e );
- $failures[] = "{$conn->getServer()}: {$e->getMessage()}";
- }
- $conn->setTrxEndCallbackSuppression( false );
- $this->applyTransactionRoundFlags( $conn );
- }
- );
-
- if ( $failures ) {
- throw new DBExpectedError(
- null,
- "$fname: Flush failed on server(s) " . implode( "\n", array_unique( $failures ) )
- );
- }
- }
-
- /**
- * Issue COMMIT on all master connections where writes where done
- * @param string $fname Caller name
- * @throws DBExpectedError
- */
- public function commitMasterChanges( $fname = __METHOD__ ) {
- $failures = [];
-
- $restore = ( $this->trxRoundId !== false );
- $this->trxRoundId = false;
- $this->forEachOpenMasterConnection(
- function ( DatabaseBase $conn ) use ( $fname, $restore, &$failures ) {
- try {
- if ( $conn->writesOrCallbacksPending() ) {
- $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
- } elseif ( $restore ) {
- $conn->flushSnapshot( $fname );
- }
- } catch ( DBError $e ) {
- MWExceptionHandler::logException( $e );
- $failures[] = "{$conn->getServer()}: {$e->getMessage()}";
- }
- if ( $restore ) {
- $this->undoTransactionRoundFlags( $conn );
- }
- }
- );
-
- if ( $failures ) {
- throw new DBExpectedError(
- null,
- "$fname: Commit failed on server(s) " . implode( "\n", array_unique( $failures ) )
- );
- }
- }
-
- /**
- * Issue all pending post-COMMIT/ROLLBACK callbacks
- * @param integer $type IDatabase::TRIGGER_* constant
- * @return Exception|null The first exception or null if there were none
- * @since 1.28
- */
- public function runMasterPostTrxCallbacks( $type ) {
- $e = null; // first exception
- $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) use ( $type, &$e ) {
- $conn->setTrxEndCallbackSuppression( false );
- if ( $conn->writesOrCallbacksPending() ) {
- // This happens if onTransactionIdle() callbacks leave callbacks on *another* DB
- // (which finished its callbacks already). Warn and recover in this case. Let the
- // callbacks run in the final commitMasterChanges() in LBFactory::shutdown().
- wfWarn( __METHOD__ . ": did not expect writes/callbacks pending." );
- return;
- } elseif ( $conn->trxLevel() ) {
- // This happens for single-DB setups where DB_REPLICA uses the master DB,
- // thus leaving an implicit read-only transaction open at this point. It
- // also happens if onTransactionIdle() callbacks leave implicit transactions
- // open on *other* DBs (which is slightly improper). Let these COMMIT on the
- // next call to commitMasterChanges(), possibly in LBFactory::shutdown().
- return;
- }
- try {
- $conn->runOnTransactionIdleCallbacks( $type );
- } catch ( Exception $ex ) {
- $e = $e ?: $ex;
- }
- try {
- $conn->runTransactionListenerCallbacks( $type );
- } catch ( Exception $ex ) {
- $e = $e ?: $ex;
- }
- } );
-
- return $e;
- }
-
- /**
- * Issue ROLLBACK only on master, only if queries were done on connection
- * @param string $fname Caller name
- * @throws DBExpectedError
- * @since 1.23
- */
- public function rollbackMasterChanges( $fname = __METHOD__ ) {
- $restore = ( $this->trxRoundId !== false );
- $this->trxRoundId = false;
- $this->forEachOpenMasterConnection(
- function ( DatabaseBase $conn ) use ( $fname, $restore ) {
- if ( $conn->writesOrCallbacksPending() ) {
- $conn->rollback( $fname, $conn::FLUSHING_ALL_PEERS );
- }
- if ( $restore ) {
- $this->undoTransactionRoundFlags( $conn );
- }
- }
- );
- }
-
- /**
- * Suppress all pending post-COMMIT/ROLLBACK callbacks
- * @return Exception|null The first exception or null if there were none
- * @since 1.28
- */
- public function suppressTransactionEndCallbacks() {
- $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) {
- $conn->setTrxEndCallbackSuppression( true );
- } );
- }
-
- /**
- * @param DatabaseBase $conn
- */
- private function applyTransactionRoundFlags( DatabaseBase $conn ) {
- if ( $conn->getFlag( DBO_DEFAULT ) ) {
- // DBO_TRX is controlled entirely by CLI mode presence with DBO_DEFAULT.
- // Force DBO_TRX even in CLI mode since a commit round is expected soon.
- $conn->setFlag( DBO_TRX, $conn::REMEMBER_PRIOR );
- // If config has explicitly requested DBO_TRX be either on or off by not
- // setting DBO_DEFAULT, then respect that. Forcing no transactions is useful
- // for things like blob stores (ExternalStore) which want auto-commit mode.
- }
- }
-
- /**
- * @param DatabaseBase $conn
- */
- private function undoTransactionRoundFlags( DatabaseBase $conn ) {
- if ( $conn->getFlag( DBO_DEFAULT ) ) {
- $conn->restoreFlags( $conn::RESTORE_PRIOR );
- }
- }
-
- /**
- * Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot
- *
- * @param string $fname Caller name
- * @since 1.28
- */
- public function flushReplicaSnapshots( $fname = __METHOD__ ) {
- $this->forEachOpenReplicaConnection( function ( DatabaseBase $conn ) {
- $conn->flushSnapshot( __METHOD__ );
- } );
- }
-
- /**
- * @return bool Whether a master connection is already open
- * @since 1.24
- */
- public function hasMasterConnection() {
- return $this->isOpen( $this->getWriterIndex() );
- }
-
- /**
- * Determine if there are pending changes in a transaction by this thread
- * @since 1.23
- * @return bool
- */
- public function hasMasterChanges() {
- $masterIndex = $this->getWriterIndex();
- foreach ( $this->mConns as $conns2 ) {
- if ( empty( $conns2[$masterIndex] ) ) {
- continue;
- }
- /** @var DatabaseBase $conn */
- foreach ( $conns2[$masterIndex] as $conn ) {
- if ( $conn->writesOrCallbacksPending() ) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Get the timestamp of the latest write query done by this thread
- * @since 1.25
- * @return float|bool UNIX timestamp or false
- */
- public function lastMasterChangeTimestamp() {
- $lastTime = false;
- $masterIndex = $this->getWriterIndex();
- foreach ( $this->mConns as $conns2 ) {
- if ( empty( $conns2[$masterIndex] ) ) {
- continue;
- }
- /** @var DatabaseBase $conn */
- foreach ( $conns2[$masterIndex] as $conn ) {
- $lastTime = max( $lastTime, $conn->lastDoneWrites() );
- }
- }
- return $lastTime;
- }
-
- /**
- * Check if this load balancer object had any recent or still
- * pending writes issued against it by this PHP thread
- *
- * @param float $age How many seconds ago is "recent" [defaults to mWaitTimeout]
- * @return bool
- * @since 1.25
- */
- public function hasOrMadeRecentMasterChanges( $age = null ) {
- $age = ( $age === null ) ? $this->mWaitTimeout : $age;
-
- return ( $this->hasMasterChanges()
- || $this->lastMasterChangeTimestamp() > microtime( true ) - $age );
- }
-
- /**
- * Get the list of callers that have pending master changes
- *
- * @return array
- * @since 1.27
- */
- public function pendingMasterChangeCallers() {
- $fnames = [];
-
- $masterIndex = $this->getWriterIndex();
- foreach ( $this->mConns as $conns2 ) {
- if ( empty( $conns2[$masterIndex] ) ) {
- continue;
- }
- /** @var DatabaseBase $conn */
- foreach ( $conns2[$masterIndex] as $conn ) {
- $fnames = array_merge( $fnames, $conn->pendingWriteCallers() );
- }
- }
-
- return $fnames;
- }
-
- /**
- * @note This method will trigger a DB connection if not yet done
- * @param string|bool $wiki Wiki ID, or false for the current wiki
- * @return bool Whether the generic connection for reads is highly "lagged"
- */
- public function getLaggedReplicaMode( $wiki = false ) {
- // No-op if there is only one DB (also avoids recursion)
- if ( !$this->laggedReplicaMode && $this->getServerCount() > 1 ) {
- try {
- // See if laggedReplicaMode gets set
- $conn = $this->getConnection( DB_REPLICA, false, $wiki );
- $this->reuseConnection( $conn );
- } catch ( DBConnectionError $e ) {
- // Avoid expensive re-connect attempts and failures
- $this->allReplicasDownMode = true;
- $this->laggedReplicaMode = true;
- }
- }
-
- return $this->laggedReplicaMode;
- }
-
- /**
- * @param bool $wiki
- * @return bool
- * @deprecated 1.28; use getLaggedReplicaMode()
- */
- public function getLaggedSlaveMode( $wiki = false ) {
- return $this->getLaggedReplicaMode( $wiki );
- }
-
- /**
- * @note This method will never cause a new DB connection
- * @return bool Whether any generic connection used for reads was highly "lagged"
- * @since 1.28
- */
- public function laggedReplicaUsed() {
- return $this->laggedReplicaMode;
- }
-
- /**
- * @return bool
- * @since 1.27
- * @deprecated Since 1.28; use laggedReplicaUsed()
- */
- public function laggedSlaveUsed() {
- return $this->laggedReplicaUsed();
- }
-
- /**
- * @note This method may trigger a DB connection if not yet done
- * @param string|bool $wiki Wiki ID, or false for the current wiki
- * @param DatabaseBase|null DB master connection; used to avoid loops [optional]
- * @return string|bool Reason the master is read-only or false if it is not
- * @since 1.27
- */
- public function getReadOnlyReason( $wiki = false, DatabaseBase $conn = null ) {
- if ( $this->readOnlyReason !== false ) {
- return $this->readOnlyReason;
- } elseif ( $this->getLaggedReplicaMode( $wiki ) ) {
- if ( $this->allReplicasDownMode ) {
- return 'The database has been automatically locked ' .
- 'until the replica database servers become available';
- } else {
- return 'The database has been automatically locked ' .
- 'while the replica database servers catch up to the master.';
- }
- } elseif ( $this->masterRunningReadOnly( $wiki, $conn ) ) {
- return 'The database master is running in read-only mode.';
- }
-
- return false;
- }
-
- /**
- * @param string $wiki Wiki ID, or false for the current wiki
- * @param DatabaseBase|null DB master connectionl used to avoid loops [optional]
- * @return bool
- */
- private function masterRunningReadOnly( $wiki, DatabaseBase $conn = null ) {
- $cache = $this->wanCache;
- $masterServer = $this->getServerName( $this->getWriterIndex() );
-
- return (bool)$cache->getWithSetCallback(
- $cache->makeGlobalKey( __CLASS__, 'server-read-only', $masterServer ),
- self::TTL_CACHE_READONLY,
- function () use ( $wiki, $conn ) {
- $this->trxProfiler->setSilenced( true );
- try {
- $dbw = $conn ?: $this->getConnection( DB_MASTER, [], $wiki );
- $readOnly = (int)$dbw->serverIsReadOnly();
- } catch ( DBError $e ) {
- $readOnly = 0;
- }
- $this->trxProfiler->setSilenced( false );
- return $readOnly;
- },
- [ 'pcTTL' => $cache::TTL_PROC_LONG, 'busyValue' => 0 ]
- );
- }
-
- /**
- * Disables/enables lag checks
- * @param null|bool $mode
- * @return bool
- */
- public function allowLagged( $mode = null ) {
- if ( $mode === null ) {
- return $this->mAllowLagged;
- }
- $this->mAllowLagged = $mode;
-
- return $this->mAllowLagged;
- }
-
- /**
- * @return bool
- */
- public function pingAll() {
- $success = true;
- $this->forEachOpenConnection( function ( DatabaseBase $conn ) use ( &$success ) {
- if ( !$conn->ping() ) {
- $success = false;
- }
- } );
-
- return $success;
- }
-
- /**
- * Call a function with each open connection object
- * @param callable $callback
- * @param array $params
- */
- public function forEachOpenConnection( $callback, array $params = [] ) {
- foreach ( $this->mConns as $connsByServer ) {
- foreach ( $connsByServer as $serverConns ) {
- foreach ( $serverConns as $conn ) {
- $mergedParams = array_merge( [ $conn ], $params );
- call_user_func_array( $callback, $mergedParams );
- }
- }
- }
- }
-
- /**
- * Call a function with each open connection object to a master
- * @param callable $callback
- * @param array $params
- * @since 1.28
- */
- public function forEachOpenMasterConnection( $callback, array $params = [] ) {
- $masterIndex = $this->getWriterIndex();
- foreach ( $this->mConns as $connsByServer ) {
- if ( isset( $connsByServer[$masterIndex] ) ) {
- /** @var DatabaseBase $conn */
- foreach ( $connsByServer[$masterIndex] as $conn ) {
- $mergedParams = array_merge( [ $conn ], $params );
- call_user_func_array( $callback, $mergedParams );
- }
- }
- }
- }
-
- /**
- * Call a function with each open replica DB connection object
- * @param callable $callback
- * @param array $params
- * @since 1.28
- */
- public function forEachOpenReplicaConnection( $callback, array $params = [] ) {
- foreach ( $this->mConns as $connsByServer ) {
- foreach ( $connsByServer as $i => $serverConns ) {
- if ( $i === $this->getWriterIndex() ) {
- continue; // skip master
- }
- foreach ( $serverConns as $conn ) {
- $mergedParams = array_merge( [ $conn ], $params );
- call_user_func_array( $callback, $mergedParams );
- }
- }
- }
- }
-
- /**
- * Get the hostname and lag time of the most-lagged replica DB
- *
- * This is useful for maintenance scripts that need to throttle their updates.
- * May attempt to open connections to replica DBs on the default DB. If there is
- * no lag, the maximum lag will be reported as -1.
- *
- * @param bool|string $wiki Wiki ID, or false for the default database
- * @return array ( host, max lag, index of max lagged host )
- */
- public function getMaxLag( $wiki = false ) {
- $maxLag = -1;
- $host = '';
- $maxIndex = 0;
-
- if ( $this->getServerCount() <= 1 ) {
- return [ $host, $maxLag, $maxIndex ]; // no replication = no lag
- }
-
- $lagTimes = $this->getLagTimes( $wiki );
- foreach ( $lagTimes as $i => $lag ) {
- if ( $this->mLoads[$i] > 0 && $lag > $maxLag ) {
- $maxLag = $lag;
- $host = $this->mServers[$i]['host'];
- $maxIndex = $i;
- }
- }
-
- return [ $host, $maxLag, $maxIndex ];
- }
-
- /**
- * Get an estimate of replication lag (in seconds) for each server
- *
- * Results are cached for a short time in memcached/process cache
- *
- * Values may be "false" if replication is too broken to estimate
- *
- * @param string|bool $wiki
- * @return int[] Map of (server index => float|int|bool)
- */
- public function getLagTimes( $wiki = false ) {
- if ( $this->getServerCount() <= 1 ) {
- return [ 0 => 0 ]; // no replication = no lag
- }
-
- # Send the request to the load monitor
- return $this->getLoadMonitor()->getLagTimes( array_keys( $this->mServers ), $wiki );
- }
-
- /**
- * Get the lag in seconds for a given connection, or zero if this load
- * balancer does not have replication enabled.
- *
- * This should be used in preference to Database::getLag() in cases where
- * replication may not be in use, since there is no way to determine if
- * replication is in use at the connection level without running
- * potentially restricted queries such as SHOW SLAVE STATUS. Using this
- * function instead of Database::getLag() avoids a fatal error in this
- * case on many installations.
- *
- * @param IDatabase $conn
- * @return int|bool Returns false on error
- */
- public function safeGetLag( IDatabase $conn ) {
- if ( $this->getServerCount() == 1 ) {
- return 0;
- } else {
- return $conn->getLag();
- }
- }
-
- /**
- * Wait for a replica DB to reach a specified master position
- *
- * This will connect to the master to get an accurate position if $pos is not given
- *
- * @param IDatabase $conn Replica DB
- * @param DBMasterPos|bool $pos Master position; default: current position
- * @param integer $timeout Timeout in seconds
- * @return bool Success
- * @since 1.27
- */
- public function safeWaitForMasterPos( IDatabase $conn, $pos = false, $timeout = 10 ) {
- if ( $this->getServerCount() == 1 || !$conn->getLBInfo( 'replica' ) ) {
- return true; // server is not a replica DB
- }
-
- $pos = $pos ?: $this->getConnection( DB_MASTER )->getMasterPos();
- if ( !( $pos instanceof DBMasterPos ) ) {
- return false; // something is misconfigured
- }
-
- $result = $conn->masterPosWait( $pos, $timeout );
- if ( $result == -1 || is_null( $result ) ) {
- $msg = __METHOD__ . ": Timed out waiting on {$conn->getServer()} pos {$pos}";
- wfDebugLog( 'replication', "$msg\n" );
- wfDebugLog( 'DBPerformance', "$msg:\n" . wfBacktrace( true ) );
- $ok = false;
- } else {
- wfDebugLog( 'replication', __METHOD__ . ": Done\n" );
- $ok = true;
- }
-
- return $ok;
- }
-
- /**
- * Clear the cache for slag lag delay times
- *
- * This is only used for testing
- */
- public function clearLagTimeCache() {
- $this->getLoadMonitor()->clearCaches();
- }
-
- /**
- * Set a callback via DatabaseBase::setTransactionListener() on
- * all current and future master connections of this load balancer
- *
- * @param string $name Callback name
- * @param callable|null $callback
- * @since 1.28
- */
- public function setTransactionListener( $name, callable $callback = null ) {
- if ( $callback ) {
- $this->trxRecurringCallbacks[$name] = $callback;
- } else {
- unset( $this->trxRecurringCallbacks[$name] );
- }
- $this->forEachOpenMasterConnection(
- function ( DatabaseBase $conn ) use ( $name, $callback ) {
- $conn->setTransactionListener( $name, $callback );
- }
- );
- }
-}
+++ /dev/null
-<?php
-/**
- * Database load monitoring.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Database
- */
-
-/**
- * An interface for database load monitoring
- *
- * @ingroup Database
- */
-interface LoadMonitor {
- /**
- * Construct a new LoadMonitor with a given LoadBalancer parent
- *
- * @param LoadBalancer $parent
- */
- public function __construct( $parent );
-
- /**
- * Perform pre-connection load ratio adjustment.
- * @param array &$loads
- * @param string|bool $group The selected query group. Default: false
- * @param string|bool $wiki Default: false
- */
- public function scaleLoads( &$loads, $group = false, $wiki = false );
-
- /**
- * Get an estimate of replication lag (in seconds) for each server
- *
- * Values may be "false" if replication is too broken to estimate
- *
- * @param array $serverIndexes
- * @param string $wiki
- *
- * @return array Map of (server index => float|int|bool)
- */
- public function getLagTimes( $serverIndexes, $wiki );
-
- /**
- * Clear any process and persistent cache of lag times
- * @since 1.27
- */
- public function clearCaches();
-}
-
-class LoadMonitorNull implements LoadMonitor {
- public function __construct( $parent ) {
- }
-
- public function scaleLoads( &$loads, $group = false, $wiki = false ) {
- }
-
- public function getLagTimes( $serverIndexes, $wiki ) {
- return array_fill_keys( $serverIndexes, 0 );
- }
-
- public function clearCaches() {
-
- }
-}
+++ /dev/null
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Database
- */
-
-/**
- * Basic MySQL load monitor with no external dependencies
- * Uses memcached to cache the replication lag for a short time
- *
- * @ingroup Database
- */
-class LoadMonitorMySQL implements LoadMonitor {
- /** @var LoadBalancer */
- public $parent;
- /** @var BagOStuff */
- protected $srvCache;
- /** @var BagOStuff */
- protected $mainCache;
-
- public function __construct( $parent ) {
- $this->parent = $parent;
-
- $this->srvCache = ObjectCache::getLocalServerInstance( 'hash' );
- $this->mainCache = ObjectCache::getLocalClusterInstance();
- }
-
- public function scaleLoads( &$loads, $group = false, $wiki = false ) {
- }
-
- public function getLagTimes( $serverIndexes, $wiki ) {
- if ( count( $serverIndexes ) == 1 && reset( $serverIndexes ) == 0 ) {
- # Single server only, just return zero without caching
- return [ 0 => 0 ];
- }
-
- $key = $this->getLagTimeCacheKey();
- # Randomize TTLs to reduce stampedes (4.0 - 5.0 sec)
- $ttl = mt_rand( 4e6, 5e6 ) / 1e6;
- # Keep keys around longer as fallbacks
- $staleTTL = 60;
-
- # (a) Check the local APC cache
- $value = $this->srvCache->get( $key );
- if ( $value && $value['timestamp'] > ( microtime( true ) - $ttl ) ) {
- wfDebugLog( 'replication', __METHOD__ . ": got lag times ($key) from local cache" );
- return $value['lagTimes']; // cache hit
- }
- $staleValue = $value ?: false;
-
- # (b) Check the shared cache and backfill APC
- $value = $this->mainCache->get( $key );
- if ( $value && $value['timestamp'] > ( microtime( true ) - $ttl ) ) {
- $this->srvCache->set( $key, $value, $staleTTL );
- wfDebugLog( 'replication', __METHOD__ . ": got lag times ($key) from main cache" );
-
- return $value['lagTimes']; // cache hit
- }
- $staleValue = $value ?: $staleValue;
-
- # (c) Cache key missing or expired; regenerate and backfill
- if ( $this->mainCache->lock( $key, 0, 10 ) ) {
- # Let this process alone update the cache value
- $cache = $this->mainCache;
- /** @noinspection PhpUnusedLocalVariableInspection */
- $unlocker = new ScopedCallback( function () use ( $cache, $key ) {
- $cache->unlock( $key );
- } );
- } elseif ( $staleValue ) {
- # Could not acquire lock but an old cache exists, so use it
- return $staleValue['lagTimes'];
- }
-
- $lagTimes = [];
- foreach ( $serverIndexes as $i ) {
- if ( $i == $this->parent->getWriterIndex() ) {
- $lagTimes[$i] = 0; // master always has no lag
- continue;
- }
-
- $conn = $this->parent->getAnyOpenConnection( $i );
- if ( $conn ) {
- $close = false; // already open
- } else {
- $conn = $this->parent->openConnection( $i, $wiki );
- $close = true; // new connection
- }
-
- if ( !$conn ) {
- $lagTimes[$i] = false;
- $host = $this->parent->getServerName( $i );
- wfDebugLog( 'replication', __METHOD__ . ": host $host (#$i) is unreachable" );
- continue;
- }
-
- $lagTimes[$i] = $conn->getLag();
- if ( $lagTimes[$i] === false ) {
- $host = $this->parent->getServerName( $i );
- wfDebugLog( 'replication', __METHOD__ . ": host $host (#$i) is not replicating?" );
- }
-
- if ( $close ) {
- # Close the connection to avoid sleeper connections piling up.
- # Note that the caller will pick one of these DBs and reconnect,
- # which is slightly inefficient, but this only matters for the lag
- # time cache miss cache, which is far less common that cache hits.
- $this->parent->closeConnection( $conn );
- }
- }
-
- # Add a timestamp key so we know when it was cached
- $value = [ 'lagTimes' => $lagTimes, 'timestamp' => microtime( true ) ];
- $this->mainCache->set( $key, $value, $staleTTL );
- $this->srvCache->set( $key, $value, $staleTTL );
- wfDebugLog( 'replication', __METHOD__ . ": re-calculated lag times ($key)" );
-
- return $value['lagTimes'];
- }
-
- public function clearCaches() {
- $key = $this->getLagTimeCacheKey();
- $this->srvCache->delete( $key );
- $this->mainCache->delete( $key );
- }
-
- private function getLagTimeCacheKey() {
- $writerIndex = $this->parent->getWriterIndex();
- // Lag is per-server, not per-DB, so key on the master DB name
- return $this->srvCache->makeGlobalKey(
- 'lag-times', $this->parent->getServerName( $writerIndex )
- );
- }
-}
*
* Usage:
* @code
- * $wgMWLoggerDefaultSpi = array(
+ * $wgMWLoggerDefaultSpi = [
* 'class' => '\\MediaWiki\\Logger\\LegacySpi',
- * );
+ * ];
* @endcode
*
* @see \MediaWiki\Logger\LoggerFactory
*
* Usage:
*
- * $wgMWLoggerDefaultSpi = array(
+ * $wgMWLoggerDefaultSpi = [
* 'class' => '\\MediaWiki\\Logger\\NullSpi',
- * );
+ * ];
*
* @see \MediaWiki\Logger\LoggerFactory
* @since 1.25
* Convenience method, calls doUpdate() on every DataUpdate in the array.
*
* @param DataUpdate[] $updates A list of DataUpdate instances
- * @param string $mode Use "enqueue" to use the job queue when possible [Default: run]
* @throws Exception
* @deprecated Since 1.28 Use DeferredUpdates::execute()
*/
- public static function runUpdates( array $updates, $mode = 'run' ) {
- DeferredUpdates::execute( $updates, $mode, DeferredUpdates::ALL );
+ public static function runUpdates( array $updates ) {
+ foreach ( $updates as $update ) {
+ $update->doUpdate();
+ }
}
}
}
/**
+ * Immediately run/queue a list of updates
+ *
* @param DeferrableUpdate[] &$queue List of DeferrableUpdate objects
* @param string $mode Use "enqueue" to use the job queue when possible
* @param integer $stage Class constant (PRESEND, POSTSEND) (since 1.28)
* @throws ErrorPageError Happens on top-level calls
* @throws Exception Happens on second-level calls
*/
- public static function execute( array &$queue, $mode, $stage ) {
+ protected static function execute( array &$queue, $mode, $stage ) {
$services = MediaWikiServices::getInstance();
$stats = $services->getStatsdDataFactory();
$lbFactory = $services->getDBLoadBalancerFactory();
$method = RequestContext::getMain()->getRequest()->getMethod();
+ $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
+
/** @var ErrorPageError $reportableError */
$reportableError = null;
/** @var DeferrableUpdate[] $updates Snapshot of queue */
// Order will be DataUpdate followed by generic DeferrableUpdate tasks
$updatesByType = [ 'data' => [], 'generic' => [] ];
foreach ( $updates as $du ) {
- $updatesByType[$du instanceof DataUpdate ? 'data' : 'generic'][] = $du;
+ if ( $du instanceof DataUpdate ) {
+ $du->setTransactionTicket( $ticket );
+ $updatesByType['data'][] = $du;
+ } else {
+ $updatesByType['generic'][] = $du;
+ }
+
$name = ( $du instanceof DeferrableCallback )
? get_class( $du ) . '-' . $du->getOrigin()
: get_class( $du );
*
* @file
*/
+use Wikimedia\Assert\Assert;
/**
* Class for handling updates to the site_stats table
*/
-class SiteStatsUpdate implements DeferrableUpdate {
+class SiteStatsUpdate implements DeferrableUpdate, MergeableUpdate {
/** @var int */
protected $edits = 0;
-
/** @var int */
protected $pages = 0;
-
/** @var int */
protected $articles = 0;
-
/** @var int */
protected $users = 0;
-
/** @var int */
protected $images = 0;
+ private static $counters = [ 'edits', 'pages', 'articles', 'users', 'images' ];
+
// @todo deprecate this constructor
function __construct( $views, $edits, $good, $pages = 0, $users = 0 ) {
$this->edits = $edits;
$this->users = $users;
}
+ public function merge( MergeableUpdate $update ) {
+ /** @var SiteStatsUpdate $update */
+ Assert::parameterType( __CLASS__, $update, '$update' );
+
+ foreach ( self::$counters as $field ) {
+ $this->$field += $update->$field;
+ }
+ }
+
/**
* @param array $deltas
* @return SiteStatsUpdate
public static function factory( array $deltas ) {
$update = new self( 0, 0, 0 );
- $fields = [ 'views', 'edits', 'pages', 'articles', 'users', 'images' ];
- foreach ( $fields as $field ) {
+ foreach ( self::$counters as $field ) {
if ( isset( $deltas[$field] ) && $deltas[$field] ) {
$update->$field = $deltas[$field];
}
* @return string|null String to output or null if any hook has been called
*/
public function runHooks( $name, $args = [] ) {
- global $wgExceptionHooks;
-
- if ( !isset( $wgExceptionHooks ) || !is_array( $wgExceptionHooks ) ) {
- return null; // Just silently ignore
- }
-
- if ( !array_key_exists( $name, $wgExceptionHooks ) ||
- !is_array( $wgExceptionHooks[$name] )
- ) {
- return null;
- }
-
- $hooks = $wgExceptionHooks[$name];
- $callargs = array_merge( [ $this ], $args );
-
- foreach ( $hooks as $hook ) {
- if (
- is_string( $hook ) ||
- ( is_array( $hook ) && count( $hook ) >= 2 && is_string( $hook[0] ) )
- ) {
- // 'function' or [ 'class', 'hook' ]
- $result = call_user_func_array( $hook, $callargs );
- } else {
- $result = null;
- }
-
- if ( is_string( $result ) ) {
- return $result;
- }
- }
- return null;
+ return MWExceptionRenderer::runHooks( $this, $name, $args );
}
/**
* It will be either HTML or plain text based on isCommandLine().
*/
public function report() {
- global $wgMimeType;
-
- if ( defined( 'MW_API' ) ) {
- // Unhandled API exception, we can't be sure that format printer is alive
- self::header( 'MediaWiki-API-Error: internal_api_error_' . get_class( $this ) );
- wfHttpError( 500, 'Internal Server Error', $this->getText() );
- } elseif ( self::isCommandLine() ) {
- MWExceptionHandler::printError( $this->getText() );
- } else {
- self::statusHeader( 500 );
- self::header( "Content-Type: $wgMimeType; charset=utf-8" );
-
- $this->reportHTML();
- }
+ MWExceptionRenderer::output( $this, MWExceptionRenderer::AS_PRETTY );
}
/**
* @param Exception|Throwable $e
*/
protected static function report( $e ) {
- global $wgShowExceptionDetails;
-
- $cmdLine = MWException::isCommandLine();
-
- if ( $e instanceof MWException ) {
- try {
- // Try and show the exception prettily, with the normal skin infrastructure
- $e->report();
- } catch ( Exception $e2 ) {
- // Exception occurred from within exception handler
- // Show a simpler message for the original exception,
- // don't try to invoke report()
- $message = "MediaWiki internal error.\n\n";
-
- if ( $wgShowExceptionDetails ) {
- $message .= 'Original exception: ' . self::getLogMessage( $e ) .
- "\nBacktrace:\n" . self::getRedactedTraceAsString( $e ) .
- "\n\nException caught inside exception handler: " . self::getLogMessage( $e2 ) .
- "\nBacktrace:\n" . self::getRedactedTraceAsString( $e2 );
- } else {
- $message .= "Exception caught inside exception handler.\n\n" .
- "Set \$wgShowExceptionDetails = true; at the bottom of LocalSettings.php " .
- "to show detailed debugging information.";
- }
-
- $message .= "\n";
-
- if ( $cmdLine ) {
- self::printError( $message );
- } else {
- echo nl2br( htmlspecialchars( $message ) ) . "\n";
- }
- }
- } else {
- if ( !$wgShowExceptionDetails ) {
- $message = self::getPublicLogMessage( $e );
- } else {
- $message = self::getLogMessage( $e ) .
- "\nBacktrace:\n" .
- self::getRedactedTraceAsString( $e ) . "\n";
- }
-
- if ( $cmdLine ) {
- self::printError( $message );
- } else {
- echo nl2br( htmlspecialchars( $message ) ) . "\n";
- }
-
- }
- }
-
- /**
- * Print a message, if possible to STDERR.
- * Use this in command line mode only (see isCommandLine)
- *
- * @param string $message Failure text
- */
- public static function printError( $message ) {
- # NOTE: STDERR may not be available, especially if php-cgi is used from the
- # command line (bug #15602). Try to produce meaningful output anyway. Using
- # echo may corrupt output to STDOUT though.
- if ( defined( 'STDERR' ) ) {
- fwrite( STDERR, $message );
- } else {
- echo $message;
+ try {
+ // Try and show the exception prettily, with the normal skin infrastructure
+ MWExceptionRenderer::output( $e, MWExceptionRenderer::AS_PRETTY );
+ } catch ( Exception $e2 ) {
+ // Exception occurred from within exception handler
+ // Show a simpler message for the original exception,
+ // don't try to invoke report()
+ MWExceptionRenderer::output( $e, MWExceptionRenderer::AS_PRETTY, $e2 );
}
}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @author Aaron Schulz
+ */
+
+/**
+ * Class to expose exceptions to the client (API bots, users, admins using CLI scripts)
+ * @since 1.28
+ */
+class MWExceptionRenderer {
+ const AS_RAW = 1; // show as text
+ const AS_PRETTY = 2; // show as HTML
+
+ /**
+ * @param Exception $e Original exception
+ * @param integer $mode MWExceptionExposer::AS_* constant
+ * @param Exception|null $eNew New exception from attempting to show the first
+ */
+ public static function output( Exception $e, $mode, Exception $eNew = null ) {
+ global $wgMimeType;
+
+ if ( $e instanceof DBConnectionError ) {
+ self::reportOutageHTML( $e );
+ return;
+ }
+
+ if ( defined( 'MW_API' ) ) {
+ // Unhandled API exception, we can't be sure that format printer is alive
+ self::header( 'MediaWiki-API-Error: internal_api_error_' . get_class( $e ) );
+ wfHttpError( 500, 'Internal Server Error', self::getText( $e ) );
+ } elseif ( self::isCommandLine() ) {
+ self::printError( self::getText( $e ) );
+ } elseif ( $mode === self::AS_PRETTY ) {
+ self::statusHeader( 500 );
+ self::header( "Content-Type: $wgMimeType; charset=utf-8" );
+ self::reportHTML( $e );
+ } else {
+ if ( $eNew ) {
+ $message = "MediaWiki internal error.\n\n";
+ if ( self::showBackTrace( $e ) ) {
+ $message .= 'Original exception: ' .
+ MWExceptionHandler::getLogMessage( $e ) .
+ "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $e ) .
+ "\n\nException caught inside exception handler: " .
+ MWExceptionHandler::getLogMessage( $eNew ) .
+ "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $eNew );
+ } else {
+ $message .= "Exception caught inside exception handler.\n\n" .
+ "Set \$wgShowExceptionDetails = true; at the bottom of LocalSettings.php " .
+ "to show detailed debugging information.";
+ }
+ $message .= "\n";
+ } else {
+ if ( self::showBackTrace( $e ) ) {
+ $message = MWExceptionHandler::getLogMessage( $e ) .
+ "\nBacktrace:\n" .
+ MWExceptionHandler::getRedactedTraceAsString( $e ) . "\n";
+ } else {
+ $message = MWExceptionHandler::getPublicLogMessage( $e );
+ }
+ }
+ if ( self::isCommandLine() ) {
+ self::printError( $message );
+ } else {
+ echo nl2br( htmlspecialchars( $message ) ) . "\n";
+ }
+ }
+ }
+
+ /**
+ * Run hook to allow extensions to modify the text of the exception
+ *
+ * Called by MWException for b/c
+ *
+ * @param Exception $e
+ * @param string $name Class name of the exception
+ * @param array $args Arguments to pass to the callback functions
+ * @return string|null String to output or null if any hook has been called
+ */
+ public static function runHooks( Exception $e, $name, $args = [] ) {
+ global $wgExceptionHooks;
+
+ if ( !isset( $wgExceptionHooks ) || !is_array( $wgExceptionHooks ) ) {
+ return null; // Just silently ignore
+ }
+
+ if ( !array_key_exists( $name, $wgExceptionHooks ) ||
+ !is_array( $wgExceptionHooks[$name] )
+ ) {
+ return null;
+ }
+
+ $hooks = $wgExceptionHooks[$name];
+ $callargs = array_merge( [ $e ], $args );
+
+ foreach ( $hooks as $hook ) {
+ if (
+ is_string( $hook ) ||
+ ( is_array( $hook ) && count( $hook ) >= 2 && is_string( $hook[0] ) )
+ ) {
+ // 'function' or [ 'class', 'hook' ]
+ $result = call_user_func_array( $hook, $callargs );
+ } else {
+ $result = null;
+ }
+
+ if ( is_string( $result ) ) {
+ return $result;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @param Exception $e
+ * @return bool Should the exception use $wgOut to output the error?
+ */
+ private static function useOutputPage( Exception $e ) {
+ // Can the extension use the Message class/wfMessage to get i18n-ed messages?
+ foreach ( $e->getTrace() as $frame ) {
+ if ( isset( $frame['class'] ) && $frame['class'] === 'LocalisationCache' ) {
+ return false;
+ }
+ }
+
+ return (
+ !empty( $GLOBALS['wgFullyInitialised'] ) &&
+ !empty( $GLOBALS['wgOut'] ) &&
+ !defined( 'MEDIAWIKI_INSTALL' )
+ );
+ }
+
+ /**
+ * Output the exception report using HTML
+ *
+ * @param Exception $e
+ */
+ private static function reportHTML( Exception $e ) {
+ global $wgOut, $wgSitename;
+
+ if ( self::useOutputPage( $e ) ) {
+ if ( $e instanceof MWException ) {
+ $wgOut->prepareErrorPage( $e->getPageTitle() );
+ } elseif ( $e instanceof DBReadOnlyError ) {
+ $wgOut->prepareErrorPage( self::msg( 'readonly', 'Database is locked' ) );
+ } elseif ( $e instanceof DBExpectedError ) {
+ $wgOut->prepareErrorPage( self::msg( 'databaseerror', 'Database error' ) );
+ } else {
+ $wgOut->prepareErrorPage( self::msg( 'internalerror', 'Internal error' ) );
+ }
+
+ $hookResult = self::runHooks( $e, get_class( $e ) );
+ if ( $hookResult ) {
+ $wgOut->addHTML( $hookResult );
+ } else {
+ // Show any custom GUI message before the details
+ if ( $e instanceof MessageSpecifier ) {
+ $wgOut->addHtml( Message::newFromSpecifier( $e )->escaped() );
+ }
+ $wgOut->addHTML( self::getHTML( $e ) );
+ }
+
+ $wgOut->output();
+ } else {
+ self::header( 'Content-Type: text/html; charset=utf-8' );
+ $pageTitle = self::msg( 'internalerror', 'Internal error' );
+ echo "<!DOCTYPE html>\n" .
+ '<html><head>' .
+ // Mimick OutputPage::setPageTitle behaviour
+ '<title>' .
+ htmlspecialchars( self::msg( 'pagetitle', "$1 - $wgSitename", $pageTitle ) ) .
+ '</title>' .
+ '<style>body { font-family: sans-serif; margin: 0; padding: 0.5em 2em; }</style>' .
+ "</head><body>\n";
+
+ $hookResult = self::runHooks( $e, get_class( $e ) . 'Raw' );
+ if ( $hookResult ) {
+ echo $hookResult;
+ } else {
+ echo self::getHTML( $e );
+ }
+
+ echo "</body></html>\n";
+ }
+ }
+
+ /**
+ * If $wgShowExceptionDetails is true, return a HTML message with a
+ * backtrace to the error, otherwise show a message to ask to set it to true
+ * to show that information.
+ *
+ * @param Exception $e
+ * @return string Html to output
+ */
+ private static function getHTML( Exception $e ) {
+ if ( self::showBackTrace( $e ) ) {
+ $html = "<div class=\"errorbox\"><p>" .
+ nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $e ) ) ) .
+ '</p><p>Backtrace:</p><p>' .
+ nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $e ) ) ) .
+ "</p></div>\n";
+ } else {
+ $logId = WebRequest::getRequestId();
+ $html = "<div class=\"errorbox\">" .
+ '[' . $logId . '] ' .
+ gmdate( 'Y-m-d H:i:s' ) . ": " .
+ self::msg( "internalerror-fatal-exception",
+ "Fatal exception of type $1",
+ get_class( $e ),
+ $logId,
+ MWExceptionHandler::getURL()
+ ) . "</div>\n" .
+ "<!-- Set \$wgShowExceptionDetails = true; " .
+ "at the bottom of LocalSettings.php to show detailed " .
+ "debugging information. -->";
+ }
+
+ return $html;
+ }
+
+ /**
+ * Get a message from i18n
+ *
+ * @param string $key Message name
+ * @param string $fallback Default message if the message cache can't be
+ * called by the exception
+ * The function also has other parameters that are arguments for the message
+ * @return string Message with arguments replaced
+ */
+ private static function msg( $key, $fallback /*[, params...] */ ) {
+ $args = array_slice( func_get_args(), 2 );
+ try {
+ return wfMessage( $key, $args )->text();
+ } catch ( Exception $e ) {
+ return wfMsgReplaceArgs( $fallback, $args );
+ }
+ }
+
+ /**
+ * @param Exception $e
+ * @return string
+ */
+ private function getText( Exception $e ) {
+ if ( self::showBackTrace( $e ) ) {
+ return MWExceptionHandler::getLogMessage( $e ) .
+ "\nBacktrace:\n" .
+ MWExceptionHandler::getRedactedTraceAsString( $e ) . "\n";
+ } else {
+ return "Set \$wgShowExceptionDetails = true; " .
+ "in LocalSettings.php to show detailed debugging information.\n";
+ }
+ }
+
+ /**
+ * @param Exception $e
+ * @return bool
+ */
+ private static function showBackTrace( Exception $e ) {
+ global $wgShowExceptionDetails, $wgShowDBErrorBacktrace;
+
+ return (
+ $wgShowExceptionDetails &&
+ ( !( $e instanceof DBError ) || $wgShowDBErrorBacktrace )
+ );
+ }
+
+ /**
+ * @return bool
+ */
+ private static function isCommandLine() {
+ return !empty( $GLOBALS['wgCommandLineMode'] );
+ }
+
+ /**
+ * @param string $header
+ */
+ private static function header( $header ) {
+ if ( !headers_sent() ) {
+ header( $header );
+ }
+ }
+
+ /**
+ * @param integer $code
+ */
+ private static function statusHeader( $code ) {
+ if ( !headers_sent() ) {
+ HttpStatus::header( $code );
+ }
+ }
+
+ /**
+ * Print a message, if possible to STDERR.
+ * Use this in command line mode only (see isCommandLine)
+ *
+ * @param string $message Failure text
+ */
+ private static function printError( $message ) {
+ // NOTE: STDERR may not be available, especially if php-cgi is used from the
+ // command line (bug #15602). Try to produce meaningful output anyway. Using
+ // echo may corrupt output to STDOUT though.
+ if ( defined( 'STDERR' ) ) {
+ fwrite( STDERR, $message );
+ } else {
+ echo $message;
+ }
+ }
+
+ /**
+ * @param Exception $e
+ */
+ private static function reportOutageHTML( Exception $e ) {
+ global $wgShowDBErrorBacktrace, $wgShowHostnames, $wgShowSQLErrors;
+
+ $sorry = htmlspecialchars( self::msg(
+ 'dberr-problems',
+ 'Sorry! This site is experiencing technical difficulties.'
+ ) );
+ $again = htmlspecialchars( self::msg(
+ 'dberr-again',
+ 'Try waiting a few minutes and reloading.'
+ ) );
+
+ if ( $wgShowHostnames || $wgShowSQLErrors ) {
+ $info = str_replace(
+ '$1',
+ Html::element( 'span', [ 'dir' => 'ltr' ], htmlspecialchars( $e->getMessage() ) ),
+ htmlspecialchars( self::msg( 'dberr-info', '($1)' ) )
+ );
+ } else {
+ $info = htmlspecialchars( self::msg(
+ 'dberr-info-hidden',
+ '(Cannot access the database)'
+ ) );
+ }
+
+ MessageCache::singleton()->disable(); // no DB access
+
+ $html = "<h1>$sorry</h1><p>$again</p><p><small>$info</small></p>";
+
+ if ( $wgShowDBErrorBacktrace ) {
+ $html .= '<p>Backtrace:</p><pre>' .
+ htmlspecialchars( $e->getTraceAsString() ) . '</pre>';
+ }
+
+ $html .= '<hr />';
+ $html .= self::googleSearchForm();
+
+ echo $html;
+ }
+
+ /**
+ * @return string
+ */
+ private static function googleSearchForm() {
+ global $wgSitename, $wgCanonicalServer, $wgRequest;
+
+ $usegoogle = htmlspecialchars( self::msg(
+ 'dberr-usegoogle',
+ 'You can try searching via Google in the meantime.'
+ ) );
+ $outofdate = htmlspecialchars( self::msg(
+ 'dberr-outofdate',
+ 'Note that their indexes of our content may be out of date.'
+ ) );
+ $googlesearch = htmlspecialchars( self::msg( 'searchbutton', 'Search' ) );
+ $search = htmlspecialchars( $wgRequest->getVal( 'search' ) );
+ $server = htmlspecialchars( $wgCanonicalServer );
+ $sitename = htmlspecialchars( $wgSitename );
+ $trygoogle = <<<EOT
+<div style="margin: 1.5em">$usegoogle<br />
+<small>$outofdate</small>
+</div>
+<form method="get" action="//www.google.com/search" id="googlesearch">
+ <input type="hidden" name="domains" value="$server" />
+ <input type="hidden" name="num" value="50" />
+ <input type="hidden" name="ie" value="UTF-8" />
+ <input type="hidden" name="oe" value="UTF-8" />
+ <input type="text" name="q" size="31" maxlength="255" value="$search" />
+ <input type="submit" name="btnG" value="$googlesearch" />
+ <p>
+ <label><input type="radio" name="sitesearch" value="$server" checked="checked" />$sitename</label>
+ <label><input type="radio" name="sitesearch" value="" />WWW</label>
+ </p>
+</form>
+EOT;
+ return $trygoogle;
+ }
+}
* @param string $titleMsg A message key to set the page title.
* Optional, default: 'exception-nologin'
* @param array $params Parameters to wfMessage().
- * Optional, default: array()
+ * Optional, default: []
*/
public function __construct(
$reasonMsg = 'exception-nologin-text',
private function loadFieldsWithTimestamp( $dbr, $fname ) {
$fieldMap = false;
- $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ),
- [ 'img_name' => $this->getName(), 'img_timestamp' => $this->getTimestamp() ],
- $fname );
+ $row = $dbr->selectRow( 'image', $this->getLazyCacheFields( 'img_' ), [
+ 'img_name' => $this->getName(),
+ 'img_timestamp' => $dbr->timestamp( $this->getTimestamp() )
+ ], $fname );
if ( $row ) {
$fieldMap = $this->unprefixRow( $row, 'img_' );
} else {
# File may have been uploaded over in the meantime; check the old versions
- $row = $dbr->selectRow( 'oldimage', $this->getLazyCacheFields( 'oi_' ),
- [ 'oi_name' => $this->getName(), 'oi_timestamp' => $this->getTimestamp() ],
- $fname );
+ $row = $dbr->selectRow( 'oldimage', $this->getLazyCacheFields( 'oi_' ), [
+ 'oi_name' => $this->getName(),
+ 'oi_timestamp' => $dbr->timestamp( $this->getTimestamp() )
+ ], $fname );
if ( $row ) {
$fieldMap = $this->unprefixRow( $row, 'oi_' );
}
}
if ( !is_string( $value ) && !is_int( $value ) ) {
- return false;
+ return $this->msg( 'htmlform-required' )->parse();
}
$validOptions = HTMLFormField::flattenOptions( $this->getOptions() );
"config-restart": "হ্যাঁ, পুনরায় চালু করুন",
"config-env-php": "পিএইচপি $1 ইন্সটল করা হয়েছে।",
"config-env-hhvm": "HHVM $1 ইনস্টল করা হয়েছে।",
+ "config-memory-raised": "পিএইচপির <code>memory_limit</code> হচ্ছে $1, বৃদ্ধি পেয়ে $2 হয়েছে।",
"config-xcache": "[http://xcache.lighttpd.net/ XCache] ইনস্টল করা হয়েছে",
"config-apc": "[http://www.php.net/apc এপিসি] ইনস্টল হয়েছে",
"config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] ইনস্টল করা হয়েছে",
"config-missing-db-host": "আপনাকে অবশ্যই \"{{int:config-db-host}}\"-এর জন্য একটি মান প্রবেশ করাতে হবে।",
"config-missing-db-server-oracle": "আপনাকে অবশ্যই \"{{int:config-db-host-oracle}}\"-এর জন্য একটি মান প্রবেশ করাতে হবে।",
"config-connection-error": "$1।\n\n\nদয়া করে প্রস্তাবকারী, ব্যবহারকারী নাম ও পাসওয়ার্ড দেখুন এবং পুনরায় চেষ্টা করুন।",
+ "config-sqlite-readonly": "ফাইল <code>$1</code> লিখনযোগ্য নয়।",
+ "config-sqlite-cant-create-db": "ডাটাবেজ ফাইল <code>$1</code> তৈরি করা যায়নি।",
"config-regenerate": "LocalSettings.php পুনরূত্পাদিত করুন →",
"config-mysql-engine": "সংরক্ষণ ইঞ্জিন:",
"config-mysql-innodb": "ইনোডিবি",
"config-license-cc-choose": "একটি স্বনির্ধারিত ক্রিয়েটিভ কমন্স লাইসেন্ট নির্বাচন করুন",
"config-email-settings": "ই-মেইল সেটিংস",
"config-email-user": "ব্যবহারকারী-থেকে-ব্যবহারকারী ই-মেইল সুবিধা সক্রিয় করো",
+ "config-email-usertalk": "ব্যবহারকারী আলাপ পাতার বিজ্ঞপ্তি সক্রিয় করো",
"config-upload-settings": "চিত্র এবং ফাইল আপলোড",
"config-upload-enable": "ফাইল আপলোড সক্রিয় করো",
"config-upload-deleted": "অপসারণকৃত ফাইলের ডিরেক্টরি:",
"config-logo": "লোগো ইউআরএল:",
+ "config-advanced-settings": "উন্নত কনফিগারেশন",
"config-memcached-servers": "মেমক্যাশেকৃত সার্ভারসমূহ:",
"config-extensions": "এক্সটেনশন",
"config-skins": "আবরণ",
"config-install-tables": "টেবিল তৈরি",
"config-install-keys": "গোপন কি তৈরি",
"config-help": "সাহায্য",
+ "config-help-tooltip": "প্রসারিত করতে ক্লিক করুন",
"mainpagetext": "<strong>মিডিয়াউইকি ইনস্টল করা হয়েছে।</strong>",
"mainpagedocfooter": "কীভাবে উইকি সফটওয়্যারটি ব্যবহারকার করবেন, তা জানতে [https://meta.wikimedia.org/wiki/Help:Contents ব্যবহারকারী সহায়িকা] দেখুন।\n\n== কোথা থেকে শুরু করবেন ==\n\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings কনফিগারেশন সেটিংস তালিকা]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ প্রশ্নোত্তরে মিডিয়াউইকি]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce মিডিয়াউইকি মুক্তির মেইলিং লিস্ট]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources আপনার ভাষার জন্য মিডিয়াউইকি স্থানীয়করণ করুন]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam আপনার উইকিতে স্প্যামের সাথে লড়াই করার উপায় সম্পর্কে জানুন]"
}
"config-email-settings": "ڕێکخستنەکانی ئیمەیڵ",
"config-install-step-done": "کرا",
"config-help": "یارمەتی",
- "mainpagetext": "'''میدیاویکی بە سەرکەوتوویی دامەزرا.'''",
- "mainpagedocfooter": "لە [https://meta.wikimedia.org/wiki/Help:Contents ڕێنوێنیی بەکارھێنەران] بۆ زانیاری سەبارەت بە بەکارھێنانی نەرمامێری ویکی کەڵک وەربگرە.\n\n== دەستپێکردن ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings پێرستی ڕێکخستنەکانی شێوەپێدان]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ پرسیارە دووپاتکراوەکانی میدیاویکی (MediaWiki FAQ)]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce پێرستی ئیمەیلی وەشانەکانی میدیاویکی]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources خۆماڵیکردنی ویکیمیدیا بۆ زمانەکەت]"
+ "mainpagetext": "<strong>میدیاویکی بە سەرکەوتوویی دامەزرا.</strong>",
+ "mainpagedocfooter": "لە [https://meta.wikimedia.org/wiki/Help:Contents ڕێنوێنیی بەکارھێنەران] بۆ زانیاری سەبارەت بە بەکارھێنانی نەرمامێری ویکی کەڵک وەربگرە.\n\n== دەستپێکردن ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings پێرستی ڕێکخستنەکانی شێوەپێدان]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ پرسیارە دووپاتکراوەکانی میدیاویکی (MediaWiki FAQ)]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce پێرستی ئیمەیلی وەشانەکانی میدیاویکی]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources خۆماڵیکردنی ویکیمیدیا بۆ زمانەکەت]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam فێربە چۆن ڕووبەڕووى ئیمەیڵە بێزارکەرەکانی ویکییەکەت دەبیتەوە]"
}
"config-subscribe": "Subscribe al [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista de diffusion pro annuncios de nove versiones].",
"config-subscribe-help": "Isto es un lista de e-mail a basse volumine pro annuncios de nove versiones, includente importante annuncios de securitate.\nTu deberea subscriber a illo e actualisar tu installation de MediaWiki quando nove versiones es editate.",
"config-subscribe-noemail": "Tu tentava abonar te al lista de diffusion pro annunciamento de nove versiones sin fornir un adresse de e-mail.\nPer favor specifica un adresse de e-mail si tu vole abonar te al lista de diffusion.",
+ "config-pingback": "Divider datos sur iste installation con le disveloppatores de MediaWiki.",
+ "config-pingback-help": "Si tu selige option, MediaWiki inviara periodicamente a https://www.mediawiki.org certe datos basic sur iste installation de MediaWiki. Iste datos include, per exemplo, le typo de systema, le version de PHP, e le programma de base de datos seligite. Le Fundation Wikimedia divide iste datos con le disveloppatores de MediaWiki pro adjutar a guidar le effortios de disveloppamento futur. Le sequente datos concernente iste systema essera inviate:\n<pre>$1</pre>",
"config-almost-done": "Tu ha quasi finite!\nTu pote ora saltar le configuration remanente e installar le wiki immediatemente.",
"config-optional-continue": "Pone me plus questiones.",
"config-optional-skip": "Isto me es jam tediose. Simplemente installa le wiki.",
"config-your-language": "A to lengua:",
"config-your-language-help": "Seleçion-a una lengua da doeuviâ durante o processo d'installaçion.",
"config-wiki-language": "A lengua do wiki:",
- "config-wiki-language-help": "Seleçion-a a lengua ch'a saiâ prevalentemente doeuviâ into wiki."
+ "config-wiki-language-help": "Seleçion-a a lengua ch'a saiâ prevalentemente doeuviâ into wiki.",
+ "config-back": "inderê",
+ "config-continue": "Continnoa →",
+ "config-page-language": "Lengua",
+ "config-page-welcome": "Benvegnui a MediaWiki!",
+ "config-page-dbconnect": "Connescion a-o database",
+ "config-page-upgrade": "Agiornamento de l'installaçion existente",
+ "config-page-dbsettings": "Impostaçioin do database",
+ "config-page-name": "Nomme",
+ "config-page-options": "Opçioin",
+ "config-page-install": "Installa",
+ "config-page-complete": "Completa!",
+ "config-page-restart": "Riavvio installaçion",
+ "config-page-readme": "Lezime",
+ "config-page-releasenotes": "Notte de verscion",
+ "config-page-copying": "Copia",
+ "config-page-upgradedoc": "Aggiornamento",
+ "config-page-existingwiki": "Wiki existenti",
+ "config-help-restart": "Ti voeu scassâ tutti i dæti sarvæ che ti t'hæ inseio e riavviâ o processo de installaçion?",
+ "config-restart": "Scì, riavvia",
+ "config-welcome": "=== Controllo de l'ambiente ===\nSaiâ eseguio di controlli de base pe vedde se questo ambiente o l'è adatto pe l'installaçion de MediaWiki.\nRegordite de includde queste informaçioin se ti domandi ascistença insce comme completâ l'installaçion.",
+ "config-copyright": "=== Copyright e termini ===\n\n$1\n\nQuesto programma o l'è un software libero; ti poeu redistriboîlo e/ò modificâlo segondo i termi da GNU General Public License, comme pubbricâ da-a Free Software Foundation; ò a verscion 2 da Liçença ò (a proppia scelta) qualunque verscion succesciva.\n\nQuesto programma o l'è distribuio inta sperança ch'o segge utile, ma SENSA ARCUNA GARANTIA; sença manco a garantia impliçita de NEGOSSIABILITÆ o de APPRICABILITÆ PE UN PARTICOLÂ SCOPO.\nS'amie a GNU General Public License pe maggioî dettaggi.\n\nQuesto programma o dev'ese distribuio insemme a <doclink href=Copying>una copia da GNU General Public License</doclink>; in caxo contraio, se ne poeu otegnî un-a scrivendo a-a Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA oppù [http://www.gnu.org/copyleft/gpl.html lezila inta ræ'].",
+ "config-sidebar": "* [https://www.mediawiki.org Paggina prinçipâ MediaWiki]\n* [https://www.mediawiki.org/wiki/Agiutto:Guidda a-i contegnui pe utenti]\n* [https://www.mediawiki.org/wiki/Manoâ:Guidda ai contegnui per admin]\n* [https://www.mediawiki.org/wiki/Manoâ:FAQ FAQ]\n----\n* <doclink href=Readme>Lezime</doclink>\n* <doclink href=ReleaseNotes>Notte de verscion</doclink>\n* <doclink href=Copying>Copie</doclink>\n* <doclink href=UpgradeDoc>Aggiornamenti</doclink>",
+ "config-env-good": "L'ambiente o l'è stæto controllou.\nL'è poscibile installâ MediaWiki.",
+ "config-env-bad": "L'ambiente o l'è stæto controllou.\nNon l'è poscibbile installâ MediaWiki.",
+ "config-env-php": "PHP $1 o l'è installou.",
+ "config-env-hhvm": "HHVM $1 o l'è installou.",
+ "config-unicode-using-intl": "Adoeuvia [http://pecl.php.net/intl l'estenscion PECL intl] pe-a normalizzaçion Unicode.",
+ "config-unicode-pure-php-warning": "'''Attençion:''' [http://pecl.php.net/intl l'estenscion PECL intl] a no l'è disponibile pe gestî a normalizzaçion Unicode, quindi se torna a-a lenta implementaçion in PHP puo.\nSe ti esegui un scito a ato traffego, ti doviesci leze arcun-e conscidiaçioin in sciâ [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalizzaçion Unicode].",
+ "config-unicode-update-warning": "'''Attençion:''' a verscion installaa do gestô pe-a normalizzaçion Unicode a l'adoeuvia una vegia verscion da libraia [http://site.icu-project.org/ do progetto ICU].\nTi doviesci [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations aggiornâ] se ti voeu doeuviâ l'Unicode.",
+ "config-no-db": "Imposcibile trovâ un driver adatto pe-o database! L'è necessaio installâ un driver pe PHP.\n{{PLURAL:$2|O seguente formato de database o l'è supportou|I seguenti formati de database son supportæ}}: $1.\n\nSe ti compilli PHP aotonomamente, riconfiguilo attivando un client database, presempio utilizzando <code>./configure --with-mysqli</code>.\nQualoa t'avesci installou PHP pe mezo de 'n pacchetto Debian ò Ubuntu, alloa ti devi installâ o pacchetto <code>php5-mysql</code> ascì."
}
"config-continue": "ಮುಂದುವರೆಸಾಲೆ →",
"config-page-language": "ಬಾಸೆ",
"config-page-welcome": "ಮಾಧ್ಯಮವಿಕಿಗ್ ಸ್ವಾಗತ",
- "config-page-dbconnect": "ದತà³\8dತಾà²\82ಶಸà²\82à²\9aಯà²\97à³\8d ಸà²\82ಪರà³\8dà²\95ಕೊರ್ಲೆ",
+ "config-page-dbconnect": "ದತà³\8dತಾà²\82ಸà³\8a ಸà²\82à²\9aಯà³\8aà²\97à³\8d à²\95à³\8aಲಿà²\95à³\86ಕೊರ್ಲೆ",
"config-page-name": "ಪುದರ್",
"config-page-options": "ಆಯ್ಕೆಲು",
"config-page-install": "ಸ್ಥಾಪಿಸಾಲೆ",
- "config-page-complete": "ಪà³\82ರà³\8dಣ!",
- "config-page-readme": "à²\8eನನà³\8d à²\93ದà³\81ಲà³\87",
- "config-page-releasenotes": "ಬà³\81ಡà³\81à²\97à³\8aಡà³\86ದ à²\9fಿಪà³\8dಪಣಿಲà³\81",
+ "config-page-complete": "ಮà³\81à²\97ಿà²\82ಡà³\8d!",
+ "config-page-readme": "à²\8eನನà³\8d à²\93ದà³\81ಲà³\86",
+ "config-page-releasenotes": "ಬುಡುಗಡೆದ ಟಿಪ್ಪಣಿಲು",
"config-page-copying": "ನಕಲ್ ಮಲ್ತೊಂದುಂಡು",
"config-page-upgradedoc": "ಪರಿಷ್ಕರಣೆ ಆವೊಂದುಂಡು",
- "config-page-existingwiki": "ಪà³\8dರಸà³\8dತà³\81ತ ವಿಕಿ",
- "config-restart": "ಸರಿ,à²\95à³\81ಡ ಪà³\8dರಾರà²\82ಠಮಲ್ಪುಲೆ",
+ "config-page-existingwiki": "à²\87ತà³\8dತà³\86ದ ವಿಕಿ",
+ "config-restart": "ಸರಿ,à²\95à³\81ಡ ಸà³\81ರà³\81 ಮಲ್ಪುಲೆ",
"config-db-type": "ದತ್ತಾಂಶಸಂಚಯ ಮಾದರಿ:",
"config-db-host-oracle": "ದತ್ತಾಂಶಸಂಚಯ TNS:",
"config-db-wiki-settings": "ಈ ವಿಕಿಯನ್ನು ಗುರುತಿಸಾಲೆ",
* @file
* @author Aaron Schulz
*/
+use MediaWiki\MediaWikiServices;
/**
* Class to handle job queues stored in the DB
* @return void
*/
protected function doWaitForBackups() {
- wfWaitForSlaves( false, $this->wiki, $this->cluster ?: false );
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $lbFactory->waitForReplication( [ 'wiki' => $this->wiki, 'cluster' => $this->cluster ] );
}
/**
* @return DBConnRef
*/
protected function getDB( $index ) {
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
$lb = ( $this->cluster !== false )
- ? wfGetLBFactory()->getExternalLB( $this->cluster, $this->wiki )
- : wfGetLB( $this->wiki );
+ ? $lbFactory->getExternalLB( $this->cluster, $this->wiki )
+ : $lbFactory->getMainLB( $this->wiki );
return $lb->getConnectionRef( $index, [], $this->wiki );
}
$this->cache->clear( 'queues-ready' );
}
}
+
+ $cache = ObjectCache::getLocalClusterInstance();
+ $cache->set(
+ $cache->makeGlobalKey( 'jobqueue', $this->wiki, 'hasjobs', self::TYPE_ANY ),
+ 'true',
+ 15
+ );
+ if ( array_intersect( array_keys( $jobsByType ), $this->getDefaultQueueTypes() ) ) {
+ $cache->set(
+ $cache->makeGlobalKey( 'jobqueue', $this->wiki, 'hasjobs', self::TYPE_DEFAULT ),
+ 'true',
+ 15
+ );
+ }
}
/**
* @since 1.23
*/
public function queuesHaveJobs( $type = self::TYPE_ANY ) {
- $key = wfMemcKey( 'jobqueue', 'queueshavejobs', $type );
$cache = ObjectCache::getLocalClusterInstance();
+ $key = $cache->makeGlobalKey( 'jobqueue', $this->wiki, 'hasjobs', $type );
$value = $cache->get( $key );
if ( $value === false ) {
MWExceptionHandler::rollbackMasterChangesAndLog( $e );
$status = false;
$error = get_class( $e ) . ': ' . $e->getMessage();
- MWExceptionHandler::logException( $e );
}
// Always attempt to call teardown() even if Job throws exception.
try {
private function commitMasterChanges( LBFactory $lbFactory, Job $job, $fnameTrxOwner ) {
global $wgJobSerialCommitThreshold;
+ $time = false;
$lb = $lbFactory->getMainLB( wfWikiID() );
if ( $wgJobSerialCommitThreshold !== false && $lb->getServerCount() > 1 ) {
// Generally, there is one master connection to the local DB
return;
}
- $ms = intval( 1000 * $dbwSerial->pendingWriteQueryDuration() );
+ $ms = intval( 1000 * $time );
$msg = $job->toString() . " COMMIT ENQUEUED [{$ms}ms of writes]";
$this->logger->info( $msg );
$this->debugCallback( $msg );
* @since 1.27
*/
class CategoryMembershipChangeJob extends Job {
+ /** @var integer|null */
+ private $ticket;
+
const ENQUEUE_FUDGE_SEC = 60;
public function __construct( Title $title, array $params ) {
}
public function run() {
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $lb = $lbFactory->getMainLB();
+ $dbw = $lb->getConnection( DB_MASTER );
+
+ $this->ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
+
$page = WikiPage::newFromID( $this->params['pageId'], WikiPage::READ_LATEST );
if ( !$page ) {
$this->setLastError( "Could not find page #{$this->params['pageId']}" );
return false; // deleted?
}
- $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
- $dbw = $lb->getConnection( DB_MASTER );
// Use a named lock so that jobs for this page see each others' changes
$lockKey = "CategoryMembershipUpdates:{$page->getId()}";
- $scopedLock = $dbw->getScopedLockAndFlush( $lockKey, __METHOD__, 10 );
+ $scopedLock = $dbw->getScopedLockAndFlush( $lockKey, __METHOD__, 3 );
if ( !$scopedLock ) {
$this->setLastError( "Could not acquire lock '$lockKey'" );
return false;
}
- $dbr = wfGetDB( DB_REPLICA, [ 'recentchanges' ] );
+ $dbr = $lb->getConnection( DB_REPLICA, [ 'recentchanges' ] );
// Wait till the replica DB is caught up so that jobs for this page see each others' changes
if ( !$lb->safeWaitForMasterPos( $dbr ) ) {
$this->setLastError( "Timed out while waiting for replica DB to catch up" );
);
// Apply all category updates in revision timestamp order
- $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
foreach ( $res as $row ) {
$this->notifyUpdatesForRevision( $lbFactory, $page, Revision::newFromRow( $row ) );
}
return; // nothing to do
}
- $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
-
$catMembChange = new CategoryMembershipChange( $title, $newRev );
$catMembChange->checkTemplateLinks();
$categoryTitle = Title::makeTitle( NS_CATEGORY, $categoryName );
$catMembChange->triggerCategoryAddedNotification( $categoryTitle );
if ( $insertCount++ && ( $insertCount % $batchSize ) == 0 ) {
- $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
+ $lbFactory->commitAndWaitForReplication( __METHOD__, $this->ticket );
}
}
$categoryTitle = Title::makeTitle( NS_CATEGORY, $categoryName );
$catMembChange->triggerCategoryRemovedNotification( $categoryTitle );
if ( $insertCount++ && ( $insertCount++ % $batchSize ) == 0 ) {
- $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
+ $lbFactory->commitAndWaitForReplication( __METHOD__, $this->ticket );
}
}
}
// enqueued will be reflected in backlink page parses when the leaf jobs run.
if ( !isset( $params['range'] ) ) {
try {
- wfGetLBFactory()->waitForReplication( [
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $lbFactory->waitForReplication( [
'wiki' => wfWikiID(),
'timeout' => self::LAG_WAIT_TIMEOUT
] );
* @return bool
*/
protected function runForTitle( Title $title ) {
- $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
+ $services = MediaWikiServices::getInstance();
+ $stats = $services->getStatsdDataFactory();
+ $lbFactory = $services->getDBLoadBalancerFactory();
+ $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
$page = WikiPage::factory( $title );
$page->loadPageData( WikiPage::READ_LATEST );
// Serialize links updates by page ID so they see each others' changes
- $scopedLock = LinksUpdate::acquirePageLock( wfGetDB( DB_MASTER ), $page->getId(), 'job' );
+ $dbw = $lbFactory->getMainLB()->getConnection( DB_MASTER );
+ /** @noinspection PhpUnusedLocalVariableInspection */
+ $scopedLock = LinksUpdate::acquirePageLock( $dbw, $page->getId(), 'job' );
// Get the latest ID *after* acquirePageLock() flushed the transaction.
// This is used to detect edits/moves after loadPageData() but before the scope lock.
// The works around the chicken/egg problem of determining the scope lock key.
$parserOutput
);
- $factory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
- $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
foreach ( $updates as $key => $update ) {
- $update->setTransactionTicket( $ticket );
// FIXME: This code probably shouldn't be here?
// Needed by things like Echo notifications which need
// to know which user caused the links update
}
foreach ( $updates as $update ) {
+ $update->setTransactionTicket( $ticket );
$update->doUpdate();
}
*
* @file
*/
+use MediaWiki\MediaWikiServices;
+
class PurgeJobUtils {
/**
* Invalidate the cache of a list of pages from a single namespace.
return;
}
- $dbw->onTransactionPreCommitOrIdle( function() use ( $dbw, $namespace, $dbkeys ) {
+ $dbw->onTransactionIdle( function() use ( $dbw, $namespace, $dbkeys ) {
+ $services = MediaWikiServices::getInstance();
+ $lbFactory = $services->getDBLoadBalancerFactory();
// Determine which pages need to be updated.
// This is necessary to prevent the job queue from smashing the DB with
// large numbers of concurrent invalidations of the same page.
__METHOD__
);
- if ( $ids === [] ) {
+ if ( !$ids ) {
return;
}
- // Do the update.
- // We still need the page_touched condition, in case the row has changed since
- // the non-locking select above.
- $dbw->update(
- 'page',
- [ 'page_touched' => $now ],
- [
- 'page_id' => $ids,
- 'page_touched < ' . $dbw->addQuotes( $now )
- ],
- __METHOD__
- );
+ $batchSize = $services->getMainConfig()->get( 'UpdateRowsPerQuery' );
+ $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
+ foreach ( array_chunk( $ids, $batchSize ) as $idBatch ) {
+ $dbw->update(
+ 'page',
+ [ 'page_touched' => $now ],
+ [
+ 'page_id' => $idBatch,
+ 'page_touched < ' . $dbw->addQuotes( $now ) // handle races
+ ],
+ __METHOD__
+ );
+ $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
+ }
} );
}
}
* @return mixed The cached value if found or the result of $callback otherwise
*/
public function getWithSetCallback( $key, callable $callback ) {
- $value = $this->get( $key );
- if ( $value === null ) {
+ if ( $this->has( $key ) ) {
+ $value = $this->get( $key );
+ } else {
$value = call_user_func( $callback );
if ( $value !== false ) {
$this->set( $key, $value );
private $timeout;
/** @var float Seconds */
private $lastWaitTime;
+ /** @var integer|null */
+ private $rusageMode;
const CONDITION_REACHED = 1;
const CONDITION_CONTINUE = 0; // evaluates as falsey
$this->condition = $condition;
$this->timeout = $timeout;
$this->busyCallbacks =& $busyCallbacks;
+
+ if ( defined( 'HHVM_VERSION' ) && PHP_OS === 'Linux' ) {
+ $this->rusageMode = 2; // RUSAGE_THREAD
+ } elseif ( function_exists( 'getrusage' ) ) {
+ $this->rusageMode = 0; // RUSAGE_SELF
+ }
}
/**
* @return float Returns 0.0 if not supported (Windows on PHP < 7)
*/
protected function getCpuTime() {
- $time = 0.0;
-
- if ( defined( 'HHVM_VERSION' ) && PHP_OS === 'Linux' ) {
- $ru = getrusage( 2 /* RUSAGE_THREAD */ );
- } else {
- $ru = getrusage( 0 /* RUSAGE_SELF */ );
- }
- if ( $ru ) {
- $time += $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6;
- $time += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6;
+ if ( $this->rusageMode === null ) {
+ return microtime( true ); // assume worst case (all time is CPU)
}
+ $ru = getrusage( $this->rusageMode );
+ $time = $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6;
+ $time += $ru['ru_stime.tv_sec'] + $ru['ru_stime.tv_usec'] / 1e6;
+
return $time;
}
// Medium attributes constants related to emulation or media type
const ATTR_EMULATION = 1;
const QOS_EMULATION_SQL = 1;
+ // Medium attributes constants related to replica consistency
+ const ATTR_SYNCWRITES = 2; // SYNC_WRITES flag support
+ const QOS_SYNCWRITES_NONE = 1; // replication only supports eventual consistency or less
+ const QOS_SYNCWRITES_BE = 2; // best effort synchronous with limited retries
+ const QOS_SYNCWRITES_QC = 3; // write quorum applied directly to state machines where R+W > N
+ const QOS_SYNCWRITES_SS = 4; // strict-serializable, nodes refuse reads if possible stale
// Generic "unknown" value that is useful for comparisons (e.g. always good enough)
const QOS_UNKNOWN = INF;
}
/** @var MemcachedClient|Memcached */
protected $client;
+ function __construct( array $params ) {
+ parent::__construct( $params );
+
+ $this->attrMap[self::ATTR_SYNCWRITES] = self::QOS_SYNCWRITES_BE; // unreliable
+ }
+
/**
* Fill in some defaults for missing keys in $params.
*
--- /dev/null
+<?php
+/**
+ * Object caching using memcached.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Cache
+ */
+
+/**
+ * A wrapper class for the PECL memcached client
+ *
+ * @ingroup Cache
+ */
+class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
+
+ /**
+ * Constructor
+ *
+ * Available parameters are:
+ * - servers: The list of IP:port combinations holding the memcached servers.
+ * - persistent: Whether to use a persistent connection
+ * - compress_threshold: The minimum size an object must be before it is compressed
+ * - timeout: The read timeout in microseconds
+ * - connect_timeout: The connect timeout in seconds
+ * - retry_timeout: Time in seconds to wait before retrying a failed connect attempt
+ * - server_failure_limit: Limit for server connect failures before it is removed
+ * - serializer: May be either "php" or "igbinary". Igbinary produces more compact
+ * values, but serialization is much slower unless the php.ini option
+ * igbinary.compact_strings is off.
+ * - use_binary_protocol Whether to enable the binary protocol (default is ASCII) (boolean)
+ * @param array $params
+ * @throws InvalidArgumentException
+ */
+ function __construct( $params ) {
+ parent::__construct( $params );
+ $params = $this->applyDefaultParams( $params );
+
+ if ( $params['persistent'] ) {
+ // The pool ID must be unique to the server/option combination.
+ // The Memcached object is essentially shared for each pool ID.
+ // We can only reuse a pool ID if we keep the config consistent.
+ $this->client = new Memcached( md5( serialize( $params ) ) );
+ if ( count( $this->client->getServerList() ) ) {
+ $this->logger->debug( __METHOD__ . ": persistent Memcached object already loaded." );
+ return; // already initialized; don't add duplicate servers
+ }
+ } else {
+ $this->client = new Memcached;
+ }
+
+ if ( $params['use_binary_protocol'] ) {
+ $this->client->setOption( Memcached::OPT_BINARY_PROTOCOL, true );
+ }
+
+ if ( isset( $params['retry_timeout'] ) ) {
+ $this->client->setOption( Memcached::OPT_RETRY_TIMEOUT, $params['retry_timeout'] );
+ }
+
+ if ( isset( $params['server_failure_limit'] ) ) {
+ $this->client->setOption( Memcached::OPT_SERVER_FAILURE_LIMIT, $params['server_failure_limit'] );
+ }
+
+ // The compression threshold is an undocumented php.ini option for some
+ // reason. There's probably not much harm in setting it globally, for
+ // compatibility with the settings for the PHP client.
+ ini_set( 'memcached.compression_threshold', $params['compress_threshold'] );
+
+ // Set timeouts
+ $this->client->setOption( Memcached::OPT_CONNECT_TIMEOUT, $params['connect_timeout'] * 1000 );
+ $this->client->setOption( Memcached::OPT_SEND_TIMEOUT, $params['timeout'] );
+ $this->client->setOption( Memcached::OPT_RECV_TIMEOUT, $params['timeout'] );
+ $this->client->setOption( Memcached::OPT_POLL_TIMEOUT, $params['timeout'] / 1000 );
+
+ // Set libketama mode since it's recommended by the documentation and
+ // is as good as any. There's no way to configure libmemcached to use
+ // hashes identical to the ones currently in use by the PHP client, and
+ // even implementing one of the libmemcached hashes in pure PHP for
+ // forwards compatibility would require MemcachedClient::get_sock() to be
+ // rewritten.
+ $this->client->setOption( Memcached::OPT_LIBKETAMA_COMPATIBLE, true );
+
+ // Set the serializer
+ switch ( $params['serializer'] ) {
+ case 'php':
+ $this->client->setOption( Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_PHP );
+ break;
+ case 'igbinary':
+ if ( !Memcached::HAVE_IGBINARY ) {
+ throw new InvalidArgumentException(
+ __CLASS__ . ': the igbinary extension is not available ' .
+ 'but igbinary serialization was requested.'
+ );
+ }
+ $this->client->setOption( Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_IGBINARY );
+ break;
+ default:
+ throw new InvalidArgumentException(
+ __CLASS__ . ': invalid value for serializer parameter'
+ );
+ }
+ $servers = [];
+ foreach ( $params['servers'] as $host ) {
+ if ( preg_match( '/^\[(.+)\]:(\d+)$/', $host, $m ) ) {
+ $servers[] = [ $m[1], (int)$m[2] ]; // (ip, port)
+ } elseif ( preg_match( '/^([^:]+):(\d+)$/', $host, $m ) ) {
+ $servers[] = [ $m[1], (int)$m[2] ]; // (ip or path, port)
+ } else {
+ $servers[] = [ $host, false ]; // (ip or path, port)
+ }
+ }
+ $this->client->addServers( $servers );
+ }
+
+ protected function applyDefaultParams( $params ) {
+ $params = parent::applyDefaultParams( $params );
+
+ if ( !isset( $params['use_binary_protocol'] ) ) {
+ $params['use_binary_protocol'] = false;
+ }
+
+ if ( !isset( $params['serializer'] ) ) {
+ $params['serializer'] = 'php';
+ }
+
+ return $params;
+ }
+
+ protected function getWithToken( $key, &$casToken, $flags = 0 ) {
+ $this->debugLog( "get($key)" );
+ $result = $this->client->get( $this->validateKeyEncoding( $key ), null, $casToken );
+ $result = $this->checkResult( $key, $result );
+ return $result;
+ }
+
+ public function set( $key, $value, $exptime = 0, $flags = 0 ) {
+ $this->debugLog( "set($key)" );
+ return $this->checkResult( $key, parent::set( $key, $value, $exptime ) );
+ }
+
+ protected function cas( $casToken, $key, $value, $exptime = 0 ) {
+ $this->debugLog( "cas($key)" );
+ return $this->checkResult( $key, parent::cas( $casToken, $key, $value, $exptime ) );
+ }
+
+ public function delete( $key ) {
+ $this->debugLog( "delete($key)" );
+ $result = parent::delete( $key );
+ if ( $result === false && $this->client->getResultCode() === Memcached::RES_NOTFOUND ) {
+ // "Not found" is counted as success in our interface
+ return true;
+ } else {
+ return $this->checkResult( $key, $result );
+ }
+ }
+
+ public function add( $key, $value, $exptime = 0 ) {
+ $this->debugLog( "add($key)" );
+ return $this->checkResult( $key, parent::add( $key, $value, $exptime ) );
+ }
+
+ public function incr( $key, $value = 1 ) {
+ $this->debugLog( "incr($key)" );
+ $result = $this->client->increment( $key, $value );
+ return $this->checkResult( $key, $result );
+ }
+
+ public function decr( $key, $value = 1 ) {
+ $this->debugLog( "decr($key)" );
+ $result = $this->client->decrement( $key, $value );
+ return $this->checkResult( $key, $result );
+ }
+
+ /**
+ * Check the return value from a client method call and take any necessary
+ * action. Returns the value that the wrapper function should return. At
+ * present, the return value is always the same as the return value from
+ * the client, but some day we might find a case where it should be
+ * different.
+ *
+ * @param string $key The key used by the caller, or false if there wasn't one.
+ * @param mixed $result The return value
+ * @return mixed
+ */
+ protected function checkResult( $key, $result ) {
+ if ( $result !== false ) {
+ return $result;
+ }
+ switch ( $this->client->getResultCode() ) {
+ case Memcached::RES_SUCCESS:
+ break;
+ case Memcached::RES_DATA_EXISTS:
+ case Memcached::RES_NOTSTORED:
+ case Memcached::RES_NOTFOUND:
+ $this->debugLog( "result: " . $this->client->getResultMessage() );
+ break;
+ default:
+ $msg = $this->client->getResultMessage();
+ $logCtx = [];
+ if ( $key !== false ) {
+ $server = $this->client->getServerByKey( $key );
+ $logCtx['memcached-server'] = "{$server['host']}:{$server['port']}";
+ $logCtx['memcached-key'] = $key;
+ $msg = "Memcached error for key \"{memcached-key}\" on server \"{memcached-server}\": $msg";
+ } else {
+ $msg = "Memcached error: $msg";
+ }
+ $this->logger->error( $msg, $logCtx );
+ $this->setLastError( BagOStuff::ERR_UNEXPECTED );
+ }
+ return $result;
+ }
+
+ public function getMulti( array $keys, $flags = 0 ) {
+ $this->debugLog( 'getMulti(' . implode( ', ', $keys ) . ')' );
+ foreach ( $keys as $key ) {
+ $this->validateKeyEncoding( $key );
+ }
+ $result = $this->client->getMulti( $keys ) ?: [];
+ return $this->checkResult( false, $result );
+ }
+
+ /**
+ * @param array $data
+ * @param int $exptime
+ * @return bool
+ */
+ public function setMulti( array $data, $exptime = 0 ) {
+ $this->debugLog( 'setMulti(' . implode( ', ', array_keys( $data ) ) . ')' );
+ foreach ( array_keys( $data ) as $key ) {
+ $this->validateKeyEncoding( $key );
+ }
+ $result = $this->client->setMulti( $data, $this->fixExpiry( $exptime ) );
+ return $this->checkResult( false, $result );
+ }
+
+ public function changeTTL( $key, $expiry = 0 ) {
+ $this->debugLog( "touch($key)" );
+ $result = $this->client->touch( $key, $expiry );
+ return $this->checkResult( $key, $result );
+ }
+}
}
// Make sure URL ends with /
$this->url = rtrim( $params['url'], '/' ) . '/';
+ // Default config, R+W > N; no locks on reads though; writes go straight to state-machine
+ $this->attrMap[self::ATTR_SYNCWRITES] = self::QOS_SYNCWRITES_QC;
}
/**
* @param integer $ttl Seconds to live. Special values are:
* - WANObjectCache::TTL_INDEFINITE: Cache forever
* @param array $opts Options map:
- * - lag : Seconds of replica DB lag. Typically, this is either the replica DB lag
- * before the data was read or, if applicable, the replica DB lag before
- * the snapshot-isolated transaction the data was read from started.
- * Default: 0 seconds
- * - since : UNIX timestamp of the data in $value. Typically, this is either
- * the current time the data was read or (if applicable) the time when
- * the snapshot-isolated transaction the data was read from started.
- * Default: 0 seconds
+ * - lag : Seconds of replica DB lag. Typically, this is either the replica DB lag
+ * before the data was read or, if applicable, the replica DB lag before
+ * the snapshot-isolated transaction the data was read from started.
+ * Use false to indicate that replication is not running.
+ * Default: 0 seconds
+ * - since : UNIX timestamp of the data in $value. Typically, this is either
+ * the current time the data was read or (if applicable) the time when
+ * the snapshot-isolated transaction the data was read from started.
+ * Default: 0 seconds
* - pending : Whether this data is possibly from an uncommitted write transaction.
- * Generally, other threads should not see values from the future and
- * they certainly should not see ones that ended up getting rolled back.
- * Default: false
+ * Generally, other threads should not see values from the future and
+ * they certainly should not see ones that ended up getting rolled back.
+ * Default: false
* - lockTSE : if excessive replication/snapshot lag is detected, then store the value
- * with this TTL and flag it as stale. This is only useful if the reads for
- * this key use getWithSetCallback() with "lockTSE" set.
- * Default: WANObjectCache::TSE_NONE
+ * with this TTL and flag it as stale. This is only useful if the reads for
+ * this key use getWithSetCallback() with "lockTSE" set.
+ * Default: WANObjectCache::TSE_NONE
+ * - staleTTL : Seconds to keep the key around if it is stale. The get()/getMulti()
+ * methods return such stale values with a $curTTL of 0, and getWithSetCallback()
+ * will call the regeneration callback in such cases, passing in the old value
+ * and its as-of time to the callback. This is useful if adaptiveTTL() is used
+ * on the old value's as-of time when it is verified as still being correct.
+ * Default: 0.
+ * @note Options added in 1.28: staleTTL
* @return bool Success
*/
final public function set( $key, $value, $ttl = 0, array $opts = [] ) {
$lockTSE = isset( $opts['lockTSE'] ) ? $opts['lockTSE'] : self::TSE_NONE;
$age = isset( $opts['since'] ) ? max( 0, $now - $opts['since'] ) : 0;
$lag = isset( $opts['lag'] ) ? $opts['lag'] : 0;
+ $staleTTL = isset( $opts['staleTTL'] ) ? $opts['staleTTL'] : 0;
// Do not cache potentially uncommitted data as it might get rolled back
if ( !empty( $opts['pending'] ) ) {
: $wrapped;
};
- return $this->cache->merge( self::VALUE_KEY_PREFIX . $key, $func, $ttl, 1 );
+ return $this->cache->merge( self::VALUE_KEY_PREFIX . $key, $func, $ttl + $staleTTL, 1 );
}
/**
* - ageNew: Consider popularity refreshes only once a key reaches this age in seconds.
* Default: WANObjectCache::AGE_NEW.
* @return mixed Value found or written to the key
+ * @note Options added in 1.28: version, busyValue, hotTTR, ageNew, pcGroup, minAsOf
* @note Callable type hints are not used to avoid class-autoloading
*/
final public function getWithSetCallback( $key, $ttl, $callback, array $opts = [] ) {
* @param string $key
* @param integer $ttl
* @param callback $callback
- * @param array $opts Options map for getWithSetCallback() which also includes:
- * - minTime: Treat values older than this UNIX timestamp as not existing. Default: null.
+ * @param array $opts Options map for getWithSetCallback()
* @param float &$asOf Cache generation timestamp of returned value [returned]
* @return mixed
* @note Callable type hints are not used to avoid class-autoloading
}
public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) {
- return $this->mergeViaCas( $key, $callback, $exptime, $attempts );
+ if ( wincache_lock( $key ) ) { // optimize with FIFO lock
+ $ok = $this->mergeViaLock( $key, $callback, $exptime, $attempts, $flags );
+ wincache_unlock( $key );
+ } else {
+ $ok = false;
+ }
+
+ return $ok;
}
}
--- /dev/null
+<?php
+/**
+ * Transaction profiling for contention
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Profiler
+ * @author Aaron Schulz
+ */
+
+use Psr\Log\LoggerInterface;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\NullLogger;
+
+/**
+ * Helper class that detects high-contention DB queries via profiling calls
+ *
+ * This class is meant to work with a DatabaseBase object, which manages queries
+ *
+ * @since 1.24
+ */
+class TransactionProfiler implements LoggerAwareInterface {
+ /** @var float Seconds */
+ protected $dbLockThreshold = 3.0;
+ /** @var float Seconds */
+ protected $eventThreshold = .25;
+ /** @var bool */
+ protected $silenced = false;
+
+ /** @var array transaction ID => (write start time, list of DBs involved) */
+ protected $dbTrxHoldingLocks = [];
+ /** @var array transaction ID => list of (query name, start time, end time) */
+ protected $dbTrxMethodTimes = [];
+
+ /** @var array */
+ protected $hits = [
+ 'writes' => 0,
+ 'queries' => 0,
+ 'conns' => 0,
+ 'masterConns' => 0
+ ];
+ /** @var array */
+ protected $expect = [
+ 'writes' => INF,
+ 'queries' => INF,
+ 'conns' => INF,
+ 'masterConns' => INF,
+ 'maxAffected' => INF,
+ 'readQueryTime' => INF,
+ 'writeQueryTime' => INF
+ ];
+ /** @var array */
+ protected $expectBy = [];
+
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
+ public function __construct() {
+ $this->setLogger( new NullLogger() );
+ }
+
+ public function setLogger( LoggerInterface $logger ) {
+ $this->logger = $logger;
+ }
+
+ /**
+ * @param bool $value
+ * @since 1.28
+ */
+ public function setSilenced( $value ) {
+ $this->silenced = $value;
+ }
+
+ /**
+ * Set performance expectations
+ *
+ * With conflicting expectations, the most narrow ones will be used
+ *
+ * @param string $event (writes,queries,conns,mConns)
+ * @param integer $value Maximum count of the event
+ * @param string $fname Caller
+ * @since 1.25
+ */
+ public function setExpectation( $event, $value, $fname ) {
+ $this->expect[$event] = isset( $this->expect[$event] )
+ ? min( $this->expect[$event], $value )
+ : $value;
+ if ( $this->expect[$event] == $value ) {
+ $this->expectBy[$event] = $fname;
+ }
+ }
+
+ /**
+ * Set multiple performance expectations
+ *
+ * With conflicting expectations, the most narrow ones will be used
+ *
+ * @param array $expects Map of (event => limit)
+ * @param $fname
+ * @since 1.26
+ */
+ public function setExpectations( array $expects, $fname ) {
+ foreach ( $expects as $event => $value ) {
+ $this->setExpectation( $event, $value, $fname );
+ }
+ }
+
+ /**
+ * Reset performance expectations and hit counters
+ *
+ * @since 1.25
+ */
+ public function resetExpectations() {
+ foreach ( $this->hits as &$val ) {
+ $val = 0;
+ }
+ unset( $val );
+ foreach ( $this->expect as &$val ) {
+ $val = INF;
+ }
+ unset( $val );
+ $this->expectBy = [];
+ }
+
+ /**
+ * Mark a DB as having been connected to with a new handle
+ *
+ * Note that there can be multiple connections to a single DB.
+ *
+ * @param string $server DB server
+ * @param string $db DB name
+ * @param bool $isMaster
+ */
+ public function recordConnection( $server, $db, $isMaster ) {
+ // Report when too many connections happen...
+ if ( $this->hits['conns']++ == $this->expect['conns'] ) {
+ $this->reportExpectationViolated( 'conns', "[connect to $server ($db)]" );
+ }
+ if ( $isMaster && $this->hits['masterConns']++ == $this->expect['masterConns'] ) {
+ $this->reportExpectationViolated( 'masterConns', "[connect to $server ($db)]" );
+ }
+ }
+
+ /**
+ * Mark a DB as in a transaction with one or more writes pending
+ *
+ * Note that there can be multiple connections to a single DB.
+ *
+ * @param string $server DB server
+ * @param string $db DB name
+ * @param string $id ID string of transaction
+ */
+ public function transactionWritingIn( $server, $db, $id ) {
+ $name = "{$server} ({$db}) (TRX#$id)";
+ if ( isset( $this->dbTrxHoldingLocks[$name] ) ) {
+ $this->logger->info( "Nested transaction for '$name' - out of sync." );
+ }
+ $this->dbTrxHoldingLocks[$name] = [
+ 'start' => microtime( true ),
+ 'conns' => [], // all connections involved
+ ];
+ $this->dbTrxMethodTimes[$name] = [];
+
+ foreach ( $this->dbTrxHoldingLocks as $name => &$info ) {
+ // Track all DBs in transactions for this transaction
+ $info['conns'][$name] = 1;
+ }
+ }
+
+ /**
+ * Register the name and time of a method for slow DB trx detection
+ *
+ * This assumes that all queries are synchronous (non-overlapping)
+ *
+ * @param string $query Function name or generalized SQL
+ * @param float $sTime Starting UNIX wall time
+ * @param bool $isWrite Whether this is a write query
+ * @param integer $n Number of affected rows
+ */
+ public function recordQueryCompletion( $query, $sTime, $isWrite = false, $n = 0 ) {
+ $eTime = microtime( true );
+ $elapsed = ( $eTime - $sTime );
+
+ if ( $isWrite && $n > $this->expect['maxAffected'] ) {
+ $this->logger->info(
+ "Query affected $n row(s):\n" . $query . "\n" .
+ ( new RuntimeException() )->getTraceAsString() );
+ }
+
+ // Report when too many writes/queries happen...
+ if ( $this->hits['queries']++ == $this->expect['queries'] ) {
+ $this->reportExpectationViolated( 'queries', $query );
+ }
+ if ( $isWrite && $this->hits['writes']++ == $this->expect['writes'] ) {
+ $this->reportExpectationViolated( 'writes', $query );
+ }
+ // Report slow queries...
+ if ( !$isWrite && $elapsed > $this->expect['readQueryTime'] ) {
+ $this->reportExpectationViolated( 'readQueryTime', $query, $elapsed );
+ }
+ if ( $isWrite && $elapsed > $this->expect['writeQueryTime'] ) {
+ $this->reportExpectationViolated( 'writeQueryTime', $query, $elapsed );
+ }
+
+ if ( !$this->dbTrxHoldingLocks ) {
+ // Short-circuit
+ return;
+ } elseif ( !$isWrite && $elapsed < $this->eventThreshold ) {
+ // Not an important query nor slow enough
+ return;
+ }
+
+ foreach ( $this->dbTrxHoldingLocks as $name => $info ) {
+ $lastQuery = end( $this->dbTrxMethodTimes[$name] );
+ if ( $lastQuery ) {
+ // Additional query in the trx...
+ $lastEnd = $lastQuery[2];
+ if ( $sTime >= $lastEnd ) { // sanity check
+ if ( ( $sTime - $lastEnd ) > $this->eventThreshold ) {
+ // Add an entry representing the time spent doing non-queries
+ $this->dbTrxMethodTimes[$name][] = [ '...delay...', $lastEnd, $sTime ];
+ }
+ $this->dbTrxMethodTimes[$name][] = [ $query, $sTime, $eTime ];
+ }
+ } else {
+ // First query in the trx...
+ if ( $sTime >= $info['start'] ) { // sanity check
+ $this->dbTrxMethodTimes[$name][] = [ $query, $sTime, $eTime ];
+ }
+ }
+ }
+ }
+
+ /**
+ * Mark a DB as no longer in a transaction
+ *
+ * This will check if locks are possibly held for longer than
+ * needed and log any affected transactions to a special DB log.
+ * Note that there can be multiple connections to a single DB.
+ *
+ * @param string $server DB server
+ * @param string $db DB name
+ * @param string $id ID string of transaction
+ * @param float $writeTime Time spent in write queries
+ */
+ public function transactionWritingOut( $server, $db, $id, $writeTime = 0.0 ) {
+ $name = "{$server} ({$db}) (TRX#$id)";
+ if ( !isset( $this->dbTrxMethodTimes[$name] ) ) {
+ $this->logger->info( "Detected no transaction for '$name' - out of sync." );
+ return;
+ }
+
+ $slow = false;
+
+ // Warn if too much time was spend writing...
+ if ( $writeTime > $this->expect['writeQueryTime'] ) {
+ $this->reportExpectationViolated(
+ 'writeQueryTime',
+ "[transaction $id writes to {$server} ({$db})]",
+ $writeTime
+ );
+ $slow = true;
+ }
+ // Fill in the last non-query period...
+ $lastQuery = end( $this->dbTrxMethodTimes[$name] );
+ if ( $lastQuery ) {
+ $now = microtime( true );
+ $lastEnd = $lastQuery[2];
+ if ( ( $now - $lastEnd ) > $this->eventThreshold ) {
+ $this->dbTrxMethodTimes[$name][] = [ '...delay...', $lastEnd, $now ];
+ }
+ }
+ // Check for any slow queries or non-query periods...
+ foreach ( $this->dbTrxMethodTimes[$name] as $info ) {
+ $elapsed = ( $info[2] - $info[1] );
+ if ( $elapsed >= $this->dbLockThreshold ) {
+ $slow = true;
+ break;
+ }
+ }
+ if ( $slow ) {
+ $dbs = implode( ', ', array_keys( $this->dbTrxHoldingLocks[$name]['conns'] ) );
+ $msg = "Sub-optimal transaction on DB(s) [{$dbs}]:\n";
+ foreach ( $this->dbTrxMethodTimes[$name] as $i => $info ) {
+ list( $query, $sTime, $end ) = $info;
+ $msg .= sprintf( "%d\t%.6f\t%s\n", $i, ( $end - $sTime ), $query );
+ }
+ $this->logger->info( $msg );
+ }
+ unset( $this->dbTrxHoldingLocks[$name] );
+ unset( $this->dbTrxMethodTimes[$name] );
+ }
+
+ /**
+ * @param string $expect
+ * @param string $query
+ * @param string|float|int $actual [optional]
+ */
+ protected function reportExpectationViolated( $expect, $query, $actual = null ) {
+ if ( $this->silenced ) {
+ return;
+ }
+
+ $n = $this->expect[$expect];
+ $by = $this->expectBy[$expect];
+ $actual = ( $actual !== null ) ? " (actual: $actual)" : "";
+
+ $this->logger->info(
+ "Expectation ($expect <= $n) by $by not met$actual:\n$query\n" .
+ ( new RuntimeException() )->getTraceAsString()
+ );
+ }
+}
--- /dev/null
+<?php
+/**
+ * Generator of database load balancing objects.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Database
+ */
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use MediaWiki\Logger\LoggerFactory;
+
+/**
+ * Class for ensuring a consistent ordering of events as seen by the user, despite replication.
+ * Kind of like Hawking's [[Chronology Protection Agency]].
+ */
+class ChronologyProtector implements LoggerAwareInterface{
+ /** @var BagOStuff */
+ protected $store;
+ /** @var LoggerInterface */
+ protected $logger;
+
+ /** @var string Storage key name */
+ protected $key;
+ /** @var string Hash of client parameters */
+ protected $clientId;
+ /** @var float|null Minimum UNIX timestamp of 1+ expected startup positions */
+ protected $waitForPosTime;
+ /** @var int Max seconds to wait on positions to appear */
+ protected $waitForPosTimeout = self::POS_WAIT_TIMEOUT;
+ /** @var bool Whether to no-op all method calls */
+ protected $enabled = true;
+ /** @var bool Whether to check and wait on positions */
+ protected $wait = true;
+
+ /** @var bool Whether the client data was loaded */
+ protected $initialized = false;
+ /** @var DBMasterPos[] Map of (DB master name => position) */
+ protected $startupPositions = [];
+ /** @var DBMasterPos[] Map of (DB master name => position) */
+ protected $shutdownPositions = [];
+ /** @var float[] Map of (DB master name => 1) */
+ protected $shutdownTouchDBs = [];
+
+ /** @var integer Seconds to store positions */
+ const POSITION_TTL = 60;
+ /** @var integer Max time to wait for positions to appear */
+ const POS_WAIT_TIMEOUT = 5;
+
+ /**
+ * @param BagOStuff $store
+ * @param array $client Map of (ip: <IP>, agent: <user-agent>)
+ * @param float $posTime UNIX timestamp
+ * @since 1.27
+ */
+ public function __construct( BagOStuff $store, array $client, $posTime = null ) {
+ $this->store = $store;
+ $this->clientId = md5( $client['ip'] . "\n" . $client['agent'] );
+ $this->key = $store->makeGlobalKey( __CLASS__, $this->clientId );
+ $this->waitForPosTime = $posTime;
+ $this->logger = new \Psr\Log\NullLogger();
+ }
+
+ public function setLogger( LoggerInterface $logger ) {
+ $this->logger = $logger;
+ }
+
+ /**
+ * @param bool $enabled Whether to no-op all method calls
+ * @since 1.27
+ */
+ public function setEnabled( $enabled ) {
+ $this->enabled = $enabled;
+ }
+
+ /**
+ * @param bool $enabled Whether to check and wait on positions
+ * @since 1.27
+ */
+ public function setWaitEnabled( $enabled ) {
+ $this->wait = $enabled;
+ }
+
+ /**
+ * Initialise a LoadBalancer to give it appropriate chronology protection.
+ *
+ * If the stash has a previous master position recorded, this will try to
+ * make sure that the next query to a replica DB of that master will see changes up
+ * to that position by delaying execution. The delay may timeout and allow stale
+ * data if no non-lagged replica DBs are available.
+ *
+ * @param LoadBalancer $lb
+ * @return void
+ */
+ public function initLB( LoadBalancer $lb ) {
+ if ( !$this->enabled || $lb->getServerCount() <= 1 ) {
+ return; // non-replicated setup or disabled
+ }
+
+ $this->initPositions();
+
+ $masterName = $lb->getServerName( $lb->getWriterIndex() );
+ if ( !empty( $this->startupPositions[$masterName] ) ) {
+ $pos = $this->startupPositions[$masterName];
+ $this->logger->info( __METHOD__ . ": LB for '$masterName' set to pos $pos\n" );
+ $lb->waitFor( $pos );
+ }
+ }
+
+ /**
+ * Notify the ChronologyProtector that the LoadBalancer is about to shut
+ * down. Saves replication positions.
+ *
+ * @param LoadBalancer $lb
+ * @return void
+ */
+ public function shutdownLB( LoadBalancer $lb ) {
+ if ( !$this->enabled ) {
+ return; // not enabled
+ } elseif ( !$lb->hasOrMadeRecentMasterChanges( INF ) ) {
+ // Only save the position if writes have been done on the connection
+ return;
+ }
+
+ $masterName = $lb->getServerName( $lb->getWriterIndex() );
+ if ( $lb->getServerCount() > 1 ) {
+ $pos = $lb->getMasterPos();
+ $this->logger->info( __METHOD__ . ": LB for '$masterName' has pos $pos\n" );
+ $this->shutdownPositions[$masterName] = $pos;
+ } else {
+ $this->logger->info( __METHOD__ . ": DB '$masterName' touched\n" );
+ }
+ $this->shutdownTouchDBs[$masterName] = 1;
+ }
+
+ /**
+ * Notify the ChronologyProtector that the LBFactory is done calling shutdownLB() for now.
+ * May commit chronology data to persistent storage.
+ *
+ * @param callable|null $workCallback Work to do instead of waiting on syncing positions
+ * @param string $mode One of (sync, async); whether to wait on remote datacenters
+ * @return DBMasterPos[] Empty on success; returns the (db name => position) map on failure
+ */
+ public function shutdown( callable $workCallback = null, $mode = 'sync' ) {
+ if ( !$this->enabled ) {
+ return [];
+ }
+
+ $store = $this->store;
+ // Some callers might want to know if a user recently touched a DB.
+ // These writes do not need to block on all datacenters receiving them.
+ foreach ( $this->shutdownTouchDBs as $dbName => $unused ) {
+ $store->set(
+ $this->getTouchedKey( $this->store, $dbName ),
+ microtime( true ),
+ $store::TTL_DAY
+ );
+ }
+
+ if ( !count( $this->shutdownPositions ) ) {
+ return []; // nothing to save
+ }
+
+ $this->logger->info( __METHOD__ . ": saving master pos for " .
+ implode( ', ', array_keys( $this->shutdownPositions ) ) . "\n"
+ );
+
+ // CP-protected writes should overwhemingly go to the master datacenter, so get DC-local
+ // lock to merge the values. Use a DC-local get() and a synchronous all-DC set(). This
+ // makes it possible for the BagOStuff class to write in parallel to all DCs with one RTT.
+ if ( $store->lock( $this->key, 3 ) ) {
+ if ( $workCallback ) {
+ // Let the store run the work before blocking on a replication sync barrier. By the
+ // time it's done with the work, the barrier should be fast if replication caught up.
+ $store->addBusyCallback( $workCallback );
+ }
+ $ok = $store->set(
+ $this->key,
+ self::mergePositions( $store->get( $this->key ), $this->shutdownPositions ),
+ self::POSITION_TTL,
+ ( $mode === 'sync' ) ? $store::WRITE_SYNC : 0
+ );
+ $store->unlock( $this->key );
+ } else {
+ $ok = false;
+ }
+
+ if ( !$ok ) {
+ $bouncedPositions = $this->shutdownPositions;
+ // Raced out too many times or stash is down
+ $this->logger->warning( __METHOD__ . ": failed to save master pos for " .
+ implode( ', ', array_keys( $this->shutdownPositions ) ) . "\n"
+ );
+ } elseif ( $mode === 'sync' &&
+ $store->getQoS( $store::ATTR_SYNCWRITES ) < $store::QOS_SYNCWRITES_BE
+ ) {
+ // Positions may not be in all datacenters, force LBFactory to play it safe
+ $this->logger->info( __METHOD__ . ": store may not support synchronous writes." );
+ $bouncedPositions = $this->shutdownPositions;
+ } else {
+ $bouncedPositions = [];
+ }
+
+ return $bouncedPositions;
+ }
+
+ /**
+ * @param string $dbName DB master name (e.g. "db1052")
+ * @return float|bool UNIX timestamp when client last touched the DB; false if not on record
+ * @since 1.28
+ */
+ public function getTouched( $dbName ) {
+ return $this->store->get( $this->getTouchedKey( $this->store, $dbName ) );
+ }
+
+ /**
+ * @param BagOStuff $store
+ * @param string $dbName
+ * @return string
+ */
+ private function getTouchedKey( BagOStuff $store, $dbName ) {
+ return $store->makeGlobalKey( __CLASS__, 'mtime', $this->clientId, $dbName );
+ }
+
+ /**
+ * Load in previous master positions for the client
+ */
+ protected function initPositions() {
+ if ( $this->initialized ) {
+ return;
+ }
+
+ $this->initialized = true;
+ if ( $this->wait ) {
+ // If there is an expectation to see master positions with a certain min
+ // timestamp, then block until they appear, or until a timeout is reached.
+ if ( $this->waitForPosTime > 0.0 ) {
+ $data = null;
+ $loop = new WaitConditionLoop(
+ function () use ( &$data ) {
+ $data = $this->store->get( $this->key );
+
+ return ( self::minPosTime( $data ) >= $this->waitForPosTime )
+ ? WaitConditionLoop::CONDITION_REACHED
+ : WaitConditionLoop::CONDITION_CONTINUE;
+ },
+ $this->waitForPosTimeout
+ );
+ $result = $loop->invoke();
+ $waitedMs = $loop->getLastWaitTime() * 1e3;
+
+ if ( $result == $loop::CONDITION_REACHED ) {
+ $msg = "expected and found pos time {$this->waitForPosTime} ({$waitedMs}ms)";
+ $this->logger->debug( $msg );
+ } else {
+ $msg = "expected but missed pos time {$this->waitForPosTime} ({$waitedMs}ms)";
+ $this->logger->info( $msg );
+ }
+ } else {
+ $data = $this->store->get( $this->key );
+ }
+
+ $this->startupPositions = $data ? $data['positions'] : [];
+ $this->logger->info( __METHOD__ . ": key is {$this->key} (read)\n" );
+ } else {
+ $this->startupPositions = [];
+ $this->logger->info( __METHOD__ . ": key is {$this->key} (unread)\n" );
+ }
+ }
+
+ /**
+ * @param array|bool $data
+ * @return float|null
+ */
+ private static function minPosTime( $data ) {
+ if ( !isset( $data['positions'] ) ) {
+ return null;
+ }
+
+ $min = null;
+ foreach ( $data['positions'] as $pos ) {
+ /** @var DBMasterPos $pos */
+ $min = $min ? min( $pos->asOfTime(), $min ) : $pos->asOfTime();
+ }
+
+ return $min;
+ }
+
+ /**
+ * @param array|bool $curValue
+ * @param DBMasterPos[] $shutdownPositions
+ * @return array
+ */
+ private static function mergePositions( $curValue, array $shutdownPositions ) {
+ /** @var $curPositions DBMasterPos[] */
+ if ( $curValue === false ) {
+ $curPositions = $shutdownPositions;
+ } else {
+ $curPositions = $curValue['positions'];
+ // Use the newest positions for each DB master
+ foreach ( $shutdownPositions as $db => $pos ) {
+ if ( !isset( $curPositions[$db] )
+ || $pos->asOfTime() > $curPositions[$db]->asOfTime()
+ ) {
+ $curPositions[$db] = $pos;
+ }
+ }
+ }
+
+ return [ 'positions' => $curPositions ];
+ }
+}
--- /dev/null
+<?php
+/**
+ * Helper class to handle automatically marking connections as reusable (via RAII pattern)
+ * as well handling deferring the actual network connection until the handle is used
+ *
+ * @note: proxy methods are defined explicity to avoid interface errors
+ * @ingroup Database
+ * @since 1.22
+ */
+class DBConnRef implements IDatabase {
+ /** @var ILoadBalancer */
+ private $lb;
+
+ /** @var IDatabase|null Live connection handle */
+ private $conn;
+
+ /** @var array|null */
+ private $params;
+
+ const FLD_INDEX = 0;
+ const FLD_GROUP = 1;
+ const FLD_WIKI = 2;
+
+ /**
+ * @param ILoadBalancer $lb
+ * @param IDatabase|array $conn Connection or (server index, group, wiki ID)
+ */
+ public function __construct( ILoadBalancer $lb, $conn ) {
+ $this->lb = $lb;
+ if ( $conn instanceof IDatabase ) {
+ $this->conn = $conn; // live handle
+ } elseif ( count( $conn ) >= 3 && $conn[self::FLD_WIKI] !== false ) {
+ $this->params = $conn;
+ } else {
+ throw new InvalidArgumentException( "Missing lazy connection arguments." );
+ }
+ }
+
+ function __call( $name, array $arguments ) {
+ if ( $this->conn === null ) {
+ list( $db, $groups, $wiki ) = $this->params;
+ $this->conn = $this->lb->getConnection( $db, $groups, $wiki );
+ }
+
+ return call_user_func_array( [ $this->conn, $name ], $arguments );
+ }
+
+ public function getServerInfo() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function bufferResults( $buffer = null ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function trxLevel() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function trxTimestamp() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function explicitTrxActive() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function tablePrefix( $prefix = null ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function dbSchema( $schema = null ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getLBInfo( $name = null ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function setLBInfo( $name, $value = null ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function setLazyMasterHandle( IDatabase $conn ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function implicitGroupby() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function implicitOrderby() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function lastQuery() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function doneWrites() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function lastDoneWrites() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function writesPending() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function writesOrCallbacksPending() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function pendingWriteCallers() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function isOpen() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function restoreFlags( $state = self::RESTORE_PRIOR ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getFlag( $flag ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getProperty( $name ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getWikiID() {
+ if ( $this->conn === null ) {
+ // Avoid triggering a connection
+ return $this->params[self::FLD_WIKI];
+ }
+
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getType() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function open( $server, $user, $password, $dbName ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function fetchObject( $res ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function fetchRow( $res ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function numRows( $res ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function numFields( $res ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function fieldName( $res, $n ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function insertId() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function dataSeek( $res, $row ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function lastErrno() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function lastError() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function fieldInfo( $table, $field ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function affectedRows() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getSoftwareLink() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getServerVersion() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function close() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function reportConnectionError( $error = 'Unknown error' ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function freeResult( $res ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function selectField(
+ $table, $var, $cond = '', $fname = __METHOD__, $options = []
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function selectFieldValues(
+ $table, $var, $cond = '', $fname = __METHOD__, $options = []
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function select(
+ $table, $vars, $conds = '', $fname = __METHOD__,
+ $options = [], $join_conds = []
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function selectSQLText(
+ $table, $vars, $conds = '', $fname = __METHOD__,
+ $options = [], $join_conds = []
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function selectRow(
+ $table, $vars, $conds, $fname = __METHOD__,
+ $options = [], $join_conds = []
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function estimateRowCount(
+ $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = []
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function selectRowCount(
+ $tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function fieldExists( $table, $field, $fname = __METHOD__ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function indexExists( $table, $index, $fname = __METHOD__ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function tableExists( $table, $fname = __METHOD__ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function indexUnique( $table, $index ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function makeList( $a, $mode = LIST_COMMA ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function bitNot( $field ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function bitAnd( $fieldLeft, $fieldRight ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function bitOr( $fieldLeft, $fieldRight ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function buildConcat( $stringList ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function buildGroupConcatField(
+ $delim, $table, $field, $conds = '', $join_conds = []
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function selectDB( $db ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getDBname() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getServer() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function addQuotes( $s ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function buildLike() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function anyChar() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function anyString() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function nextSequenceValue( $seqName ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function upsert(
+ $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function deleteJoin(
+ $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function delete( $table, $conds, $fname = __METHOD__ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function insertSelect(
+ $destTable, $srcTable, $varMap, $conds,
+ $fname = __METHOD__, $insertOptions = [], $selectOptions = []
+ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function unionSupportsOrderAndLimit() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function unionQueries( $sqls, $all ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function conditional( $cond, $trueVal, $falseVal ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function strreplace( $orig, $old, $new ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getServerUptime() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function wasDeadlock() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function wasLockTimeout() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function wasErrorReissuable() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function wasReadOnlyError() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function masterPosWait( DBMasterPos $pos, $timeout ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getSlavePos() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getMasterPos() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function serverIsReadOnly() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function onTransactionResolution( callable $callback ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function onTransactionIdle( callable $callback ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function onTransactionPreCommitOrIdle( callable $callback ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function setTransactionListener( $name, callable $callback = null ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function startAtomic( $fname = __METHOD__ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function endAtomic( $fname = __METHOD__ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function doAtomicSection( $fname, callable $callback ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function begin( $fname = __METHOD__, $mode = IDatabase::TRANSACTION_EXPLICIT ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function commit( $fname = __METHOD__, $flush = '' ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function rollback( $fname = __METHOD__, $flush = '' ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function flushSnapshot( $fname = __METHOD__ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function listTables( $prefix = null, $fname = __METHOD__ ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function timestamp( $ts = 0 ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function timestampOrNull( $ts = null ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function ping( &$rtt = null ) {
+ return func_num_args()
+ ? $this->__call( __FUNCTION__, [ &$rtt ] )
+ : $this->__call( __FUNCTION__, [] ); // method cares about null vs missing
+ }
+
+ public function getLag() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getSessionLagStatus() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function maxListLen() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function encodeBlob( $b ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function decodeBlob( $b ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function setSessionOptions( array $options ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function setSchemaVars( $vars ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function lockIsFree( $lockName, $method ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function lock( $lockName, $method, $timeout = 5 ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function unlock( $lockName, $method ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function namedLocksEnqueue() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function getInfinity() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function encodeExpiry( $expiry ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function decodeExpiry( $expiry, $format = TS_MW ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function setBigSelects( $value = true ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ public function isReadOnly() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
+ /**
+ * Clean up the connection when out of scope
+ */
+ function __destruct() {
+ if ( $this->conn !== null ) {
+ $this->lb->reuseConnection( $this->conn );
+ }
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @defgroup Database Database
+ *
+ * This file deals with database interface functions
+ * and query specifics/optimisations.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Database
+ */
+
+/**
+ * Basic database interface for live and lazy-loaded DB handles
+ *
+ * @note: IDatabase and DBConnRef should be updated to reflect any changes
+ * @ingroup Database
+ */
+interface IDatabase {
+ /** @var int Callback triggered immediately due to no active transaction */
+ const TRIGGER_IDLE = 1;
+ /** @var int Callback triggered by COMMIT */
+ const TRIGGER_COMMIT = 2;
+ /** @var int Callback triggered by ROLLBACK */
+ const TRIGGER_ROLLBACK = 3;
+
+ /** @var string Transaction is requested by regular caller outside of the DB layer */
+ const TRANSACTION_EXPLICIT = '';
+ /** @var string Transaction is requested internally via DBO_TRX/startAtomic() */
+ const TRANSACTION_INTERNAL = 'implicit';
+
+ /** @var string Transaction operation comes from service managing all DBs */
+ const FLUSHING_ALL_PEERS = 'flush';
+ /** @var string Transaction operation comes from the database class internally */
+ const FLUSHING_INTERNAL = 'flush';
+
+ /** @var string Do not remember the prior flags */
+ const REMEMBER_NOTHING = '';
+ /** @var string Remember the prior flags */
+ const REMEMBER_PRIOR = 'remember';
+ /** @var string Restore to the prior flag state */
+ const RESTORE_PRIOR = 'prior';
+ /** @var string Restore to the initial flag state */
+ const RESTORE_INITIAL = 'initial';
+
+ /** @var string Estimate total time (RTT, scanning, waiting on locks, applying) */
+ const ESTIMATE_TOTAL = 'total';
+ /** @var string Estimate time to apply (scanning, applying) */
+ const ESTIMATE_DB_APPLY = 'apply';
+
+ /**
+ * A string describing the current software version, and possibly
+ * other details in a user-friendly way. Will be listed on Special:Version, etc.
+ * Use getServerVersion() to get machine-friendly information.
+ *
+ * @return string Version information from the database server
+ */
+ public function getServerInfo();
+
+ /**
+ * Turns buffering of SQL result sets on (true) or off (false). Default is
+ * "on".
+ *
+ * Unbuffered queries are very troublesome in MySQL:
+ *
+ * - If another query is executed while the first query is being read
+ * out, the first query is killed. This means you can't call normal
+ * MediaWiki functions while you are reading an unbuffered query result
+ * from a normal wfGetDB() connection.
+ *
+ * - Unbuffered queries cause the MySQL server to use large amounts of
+ * memory and to hold broad locks which block other queries.
+ *
+ * If you want to limit client-side memory, it's almost always better to
+ * split up queries into batches using a LIMIT clause than to switch off
+ * buffering.
+ *
+ * @param null|bool $buffer
+ * @return null|bool The previous value of the flag
+ */
+ public function bufferResults( $buffer = null );
+
+ /**
+ * Gets the current transaction level.
+ *
+ * Historically, transactions were allowed to be "nested". This is no
+ * longer supported, so this function really only returns a boolean.
+ *
+ * @return int The previous value
+ */
+ public function trxLevel();
+
+ /**
+ * Get the UNIX timestamp of the time that the transaction was established
+ *
+ * This can be used to reason about the staleness of SELECT data
+ * in REPEATABLE-READ transaction isolation level.
+ *
+ * @return float|null Returns null if there is not active transaction
+ * @since 1.25
+ */
+ public function trxTimestamp();
+
+ /**
+ * @return bool Whether an explicit transaction or atomic sections are still open
+ * @since 1.28
+ */
+ public function explicitTrxActive();
+
+ /**
+ * Get/set the table prefix.
+ * @param string $prefix The table prefix to set, or omitted to leave it unchanged.
+ * @return string The previous table prefix.
+ */
+ public function tablePrefix( $prefix = null );
+
+ /**
+ * Get/set the db schema.
+ * @param string $schema The database schema to set, or omitted to leave it unchanged.
+ * @return string The previous db schema.
+ */
+ public function dbSchema( $schema = null );
+
+ /**
+ * Get properties passed down from the server info array of the load
+ * balancer.
+ *
+ * @param string $name The entry of the info array to get, or null to get the
+ * whole array
+ *
+ * @return array|mixed|null
+ */
+ public function getLBInfo( $name = null );
+
+ /**
+ * Set the LB info array, or a member of it. If called with one parameter,
+ * the LB info array is set to that parameter. If it is called with two
+ * parameters, the member with the given name is set to the given value.
+ *
+ * @param string $name
+ * @param array $value
+ */
+ public function setLBInfo( $name, $value = null );
+
+ /**
+ * Set a lazy-connecting DB handle to the master DB (for replication status purposes)
+ *
+ * @param IDatabase $conn
+ * @since 1.27
+ */
+ public function setLazyMasterHandle( IDatabase $conn );
+
+ /**
+ * Returns true if this database does an implicit sort when doing GROUP BY
+ *
+ * @return bool
+ */
+ public function implicitGroupby();
+
+ /**
+ * Returns true if this database does an implicit order by when the column has an index
+ * For example: SELECT page_title FROM page LIMIT 1
+ *
+ * @return bool
+ */
+ public function implicitOrderby();
+
+ /**
+ * Return the last query that went through IDatabase::query()
+ * @return string
+ */
+ public function lastQuery();
+
+ /**
+ * Returns true if the connection may have been used for write queries.
+ * Should return true if unsure.
+ *
+ * @return bool
+ */
+ public function doneWrites();
+
+ /**
+ * Returns the last time the connection may have been used for write queries.
+ * Should return a timestamp if unsure.
+ *
+ * @return int|float UNIX timestamp or false
+ * @since 1.24
+ */
+ public function lastDoneWrites();
+
+ /**
+ * @return bool Whether there is a transaction open with possible write queries
+ * @since 1.27
+ */
+ public function writesPending();
+
+ /**
+ * Returns true if there is a transaction open with possible write
+ * queries or transaction pre-commit/idle callbacks waiting on it to finish.
+ * This does *not* count recurring callbacks, e.g. from setTransactionListener().
+ *
+ * @return bool
+ */
+ public function writesOrCallbacksPending();
+
+ /**
+ * Get the time spend running write queries for this transaction
+ *
+ * High times could be due to scanning, updates, locking, and such
+ *
+ * @param string $type IDatabase::ESTIMATE_* constant [default: ESTIMATE_ALL]
+ * @return float|bool Returns false if not transaction is active
+ * @since 1.26
+ */
+ public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL );
+
+ /**
+ * Get the list of method names that did write queries for this transaction
+ *
+ * @return array
+ * @since 1.27
+ */
+ public function pendingWriteCallers();
+
+ /**
+ * Is a connection to the database open?
+ * @return bool
+ */
+ public function isOpen();
+
+ /**
+ * Set a flag for this connection
+ *
+ * @param int $flag DBO_* constants from Defines.php:
+ * - DBO_DEBUG: output some debug info (same as debug())
+ * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
+ * - DBO_TRX: automatically start transactions
+ * - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode
+ * and removes it in command line mode
+ * - DBO_PERSISTENT: use persistant database connection
+ * @param string $remember IDatabase::REMEMBER_* constant [default: REMEMBER_NOTHING]
+ */
+ public function setFlag( $flag, $remember = self::REMEMBER_NOTHING );
+
+ /**
+ * Clear a flag for this connection
+ *
+ * @param int $flag DBO_* constants from Defines.php:
+ * - DBO_DEBUG: output some debug info (same as debug())
+ * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
+ * - DBO_TRX: automatically start transactions
+ * - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode
+ * and removes it in command line mode
+ * - DBO_PERSISTENT: use persistant database connection
+ * @param string $remember IDatabase::REMEMBER_* constant [default: REMEMBER_NOTHING]
+ */
+ public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING );
+
+ /**
+ * Restore the flags to their prior state before the last setFlag/clearFlag call
+ *
+ * @param string $state IDatabase::RESTORE_* constant. [default: RESTORE_PRIOR]
+ * @since 1.28
+ */
+ public function restoreFlags( $state = self::RESTORE_PRIOR );
+
+ /**
+ * Returns a boolean whether the flag $flag is set for this connection
+ *
+ * @param int $flag DBO_* constants from Defines.php:
+ * - DBO_DEBUG: output some debug info (same as debug())
+ * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
+ * - DBO_TRX: automatically start transactions
+ * - DBO_PERSISTENT: use persistant database connection
+ * @return bool
+ */
+ public function getFlag( $flag );
+
+ /**
+ * General read-only accessor
+ *
+ * @param string $name
+ * @return string
+ */
+ public function getProperty( $name );
+
+ /**
+ * @return string
+ */
+ public function getWikiID();
+
+ /**
+ * Get the type of the DBMS, as it appears in $wgDBtype.
+ *
+ * @return string
+ */
+ public function getType();
+
+ /**
+ * Open a connection to the database. Usually aborts on failure
+ *
+ * @param string $server Database server host
+ * @param string $user Database user name
+ * @param string $password Database user password
+ * @param string $dbName Database name
+ * @return bool
+ * @throws DBConnectionError
+ */
+ public function open( $server, $user, $password, $dbName );
+
+ /**
+ * Fetch the next row from the given result object, in object form.
+ * Fields can be retrieved with $row->fieldname, with fields acting like
+ * member variables.
+ * If no more rows are available, false is returned.
+ *
+ * @param ResultWrapper|stdClass $res Object as returned from IDatabase::query(), etc.
+ * @return stdClass|bool
+ * @throws DBUnexpectedError Thrown if the database returns an error
+ */
+ public function fetchObject( $res );
+
+ /**
+ * Fetch the next row from the given result object, in associative array
+ * form. Fields are retrieved with $row['fieldname'].
+ * If no more rows are available, false is returned.
+ *
+ * @param ResultWrapper $res Result object as returned from IDatabase::query(), etc.
+ * @return array|bool
+ * @throws DBUnexpectedError Thrown if the database returns an error
+ */
+ public function fetchRow( $res );
+
+ /**
+ * Get the number of rows in a result object
+ *
+ * @param mixed $res A SQL result
+ * @return int
+ */
+ public function numRows( $res );
+
+ /**
+ * Get the number of fields in a result object
+ * @see http://www.php.net/mysql_num_fields
+ *
+ * @param mixed $res A SQL result
+ * @return int
+ */
+ public function numFields( $res );
+
+ /**
+ * Get a field name in a result object
+ * @see http://www.php.net/mysql_field_name
+ *
+ * @param mixed $res A SQL result
+ * @param int $n
+ * @return string
+ */
+ public function fieldName( $res, $n );
+
+ /**
+ * Get the inserted value of an auto-increment row
+ *
+ * The value inserted should be fetched from nextSequenceValue()
+ *
+ * Example:
+ * $id = $dbw->nextSequenceValue( 'page_page_id_seq' );
+ * $dbw->insert( 'page', [ 'page_id' => $id ] );
+ * $id = $dbw->insertId();
+ *
+ * @return int
+ */
+ public function insertId();
+
+ /**
+ * Change the position of the cursor in a result object
+ * @see http://www.php.net/mysql_data_seek
+ *
+ * @param mixed $res A SQL result
+ * @param int $row
+ */
+ public function dataSeek( $res, $row );
+
+ /**
+ * Get the last error number
+ * @see http://www.php.net/mysql_errno
+ *
+ * @return int
+ */
+ public function lastErrno();
+
+ /**
+ * Get a description of the last error
+ * @see http://www.php.net/mysql_error
+ *
+ * @return string
+ */
+ public function lastError();
+
+ /**
+ * mysql_fetch_field() wrapper
+ * Returns false if the field doesn't exist
+ *
+ * @param string $table Table name
+ * @param string $field Field name
+ *
+ * @return Field
+ */
+ public function fieldInfo( $table, $field );
+
+ /**
+ * Get the number of rows affected by the last write query
+ * @see http://www.php.net/mysql_affected_rows
+ *
+ * @return int
+ */
+ public function affectedRows();
+
+ /**
+ * Returns a wikitext link to the DB's website, e.g.,
+ * return "[http://www.mysql.com/ MySQL]";
+ * Should at least contain plain text, if for some reason
+ * your database has no website.
+ *
+ * @return string Wikitext of a link to the server software's web site
+ */
+ public function getSoftwareLink();
+
+ /**
+ * A string describing the current software version, like from
+ * mysql_get_server_info().
+ *
+ * @return string Version information from the database server.
+ */
+ public function getServerVersion();
+
+ /**
+ * Closes a database connection.
+ * if it is open : commits any open transactions
+ *
+ * @throws DBError
+ * @return bool Operation success. true if already closed.
+ */
+ public function close();
+
+ /**
+ * @param string $error Fallback error message, used if none is given by DB
+ * @throws DBConnectionError
+ */
+ public function reportConnectionError( $error = 'Unknown error' );
+
+ /**
+ * Run an SQL query and return the result. Normally throws a DBQueryError
+ * on failure. If errors are ignored, returns false instead.
+ *
+ * In new code, the query wrappers select(), insert(), update(), delete(),
+ * etc. should be used where possible, since they give much better DBMS
+ * independence and automatically quote or validate user input in a variety
+ * of contexts. This function is generally only useful for queries which are
+ * explicitly DBMS-dependent and are unsupported by the query wrappers, such
+ * as CREATE TABLE.
+ *
+ * However, the query wrappers themselves should call this function.
+ *
+ * @param string $sql SQL query
+ * @param string $fname Name of the calling function, for profiling/SHOW PROCESSLIST
+ * comment (you can use __METHOD__ or add some extra info)
+ * @param bool $tempIgnore Whether to avoid throwing an exception on errors...
+ * maybe best to catch the exception instead?
+ * @throws DBError
+ * @return bool|ResultWrapper True for a successful write query, ResultWrapper object
+ * for a successful read query, or false on failure if $tempIgnore set
+ */
+ public function query( $sql, $fname = __METHOD__, $tempIgnore = false );
+
+ /**
+ * Report a query error. Log the error, and if neither the object ignore
+ * flag nor the $tempIgnore flag is set, throw a DBQueryError.
+ *
+ * @param string $error
+ * @param int $errno
+ * @param string $sql
+ * @param string $fname
+ * @param bool $tempIgnore
+ * @throws DBQueryError
+ */
+ public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false );
+
+ /**
+ * Free a result object returned by query() or select(). It's usually not
+ * necessary to call this, just use unset() or let the variable holding
+ * the result object go out of scope.
+ *
+ * @param mixed $res A SQL result
+ */
+ public function freeResult( $res );
+
+ /**
+ * A SELECT wrapper which returns a single field from a single result row.
+ *
+ * Usually throws a DBQueryError on failure. If errors are explicitly
+ * ignored, returns false on failure.
+ *
+ * If no result rows are returned from the query, false is returned.
+ *
+ * @param string|array $table Table name. See IDatabase::select() for details.
+ * @param string $var The field name to select. This must be a valid SQL
+ * fragment: do not use unvalidated user input.
+ * @param string|array $cond The condition array. See IDatabase::select() for details.
+ * @param string $fname The function name of the caller.
+ * @param string|array $options The query options. See IDatabase::select() for details.
+ *
+ * @return bool|mixed The value from the field, or false on failure.
+ */
+ public function selectField(
+ $table, $var, $cond = '', $fname = __METHOD__, $options = []
+ );
+
+ /**
+ * A SELECT wrapper which returns a list of single field values from result rows.
+ *
+ * Usually throws a DBQueryError on failure. If errors are explicitly
+ * ignored, returns false on failure.
+ *
+ * If no result rows are returned from the query, false is returned.
+ *
+ * @param string|array $table Table name. See IDatabase::select() for details.
+ * @param string $var The field name to select. This must be a valid SQL
+ * fragment: do not use unvalidated user input.
+ * @param string|array $cond The condition array. See IDatabase::select() for details.
+ * @param string $fname The function name of the caller.
+ * @param string|array $options The query options. See IDatabase::select() for details.
+ *
+ * @return bool|array The values from the field, or false on failure
+ * @since 1.25
+ */
+ public function selectFieldValues(
+ $table, $var, $cond = '', $fname = __METHOD__, $options = []
+ );
+
+ /**
+ * Execute a SELECT query constructed using the various parameters provided.
+ * See below for full details of the parameters.
+ *
+ * @param string|array $table Table name
+ * @param string|array $vars Field names
+ * @param string|array $conds Conditions
+ * @param string $fname Caller function name
+ * @param array $options Query options
+ * @param array $join_conds Join conditions
+ *
+ *
+ * @param string|array $table
+ *
+ * May be either an array of table names, or a single string holding a table
+ * name. If an array is given, table aliases can be specified, for example:
+ *
+ * [ 'a' => 'user' ]
+ *
+ * This includes the user table in the query, with the alias "a" available
+ * for use in field names (e.g. a.user_name).
+ *
+ * All of the table names given here are automatically run through
+ * DatabaseBase::tableName(), which causes the table prefix (if any) to be
+ * added, and various other table name mappings to be performed.
+ *
+ * Do not use untrusted user input as a table name. Alias names should
+ * not have characters outside of the Basic multilingual plane.
+ *
+ * @param string|array $vars
+ *
+ * May be either a field name or an array of field names. The field names
+ * can be complete fragments of SQL, for direct inclusion into the SELECT
+ * query. If an array is given, field aliases can be specified, for example:
+ *
+ * [ 'maxrev' => 'MAX(rev_id)' ]
+ *
+ * This includes an expression with the alias "maxrev" in the query.
+ *
+ * If an expression is given, care must be taken to ensure that it is
+ * DBMS-independent.
+ *
+ * Untrusted user input must not be passed to this parameter.
+ *
+ * @param string|array $conds
+ *
+ * May be either a string containing a single condition, or an array of
+ * conditions. If an array is given, the conditions constructed from each
+ * element are combined with AND.
+ *
+ * Array elements may take one of two forms:
+ *
+ * - Elements with a numeric key are interpreted as raw SQL fragments.
+ * - Elements with a string key are interpreted as equality conditions,
+ * where the key is the field name.
+ * - If the value of such an array element is a scalar (such as a
+ * string), it will be treated as data and thus quoted appropriately.
+ * If it is null, an IS NULL clause will be added.
+ * - If the value is an array, an IN (...) clause will be constructed
+ * from its non-null elements, and an IS NULL clause will be added
+ * if null is present, such that the field may match any of the
+ * elements in the array. The non-null elements will be quoted.
+ *
+ * Note that expressions are often DBMS-dependent in their syntax.
+ * DBMS-independent wrappers are provided for constructing several types of
+ * expression commonly used in condition queries. See:
+ * - IDatabase::buildLike()
+ * - IDatabase::conditional()
+ *
+ * Untrusted user input is safe in the values of string keys, however untrusted
+ * input must not be used in the array key names or in the values of numeric keys.
+ * Escaping of untrusted input used in values of numeric keys should be done via
+ * IDatabase::addQuotes()
+ *
+ * @param string|array $options
+ *
+ * Optional: Array of query options. Boolean options are specified by
+ * including them in the array as a string value with a numeric key, for
+ * example:
+ *
+ * [ 'FOR UPDATE' ]
+ *
+ * The supported options are:
+ *
+ * - OFFSET: Skip this many rows at the start of the result set. OFFSET
+ * with LIMIT can theoretically be used for paging through a result set,
+ * but this is discouraged in MediaWiki for performance reasons.
+ *
+ * - LIMIT: Integer: return at most this many rows. The rows are sorted
+ * and then the first rows are taken until the limit is reached. LIMIT
+ * is applied to a result set after OFFSET.
+ *
+ * - FOR UPDATE: Boolean: lock the returned rows so that they can't be
+ * changed until the next COMMIT.
+ *
+ * - DISTINCT: Boolean: return only unique result rows.
+ *
+ * - GROUP BY: May be either an SQL fragment string naming a field or
+ * expression to group by, or an array of such SQL fragments.
+ *
+ * - HAVING: May be either an string containing a HAVING clause or an array of
+ * conditions building the HAVING clause. If an array is given, the conditions
+ * constructed from each element are combined with AND.
+ *
+ * - ORDER BY: May be either an SQL fragment giving a field name or
+ * expression to order by, or an array of such SQL fragments.
+ *
+ * - USE INDEX: This may be either a string giving the index name to use
+ * for the query, or an array. If it is an associative array, each key
+ * gives the table name (or alias), each value gives the index name to
+ * use for that table. All strings are SQL fragments and so should be
+ * validated by the caller.
+ *
+ * - EXPLAIN: In MySQL, this causes an EXPLAIN SELECT query to be run,
+ * instead of SELECT.
+ *
+ * And also the following boolean MySQL extensions, see the MySQL manual
+ * for documentation:
+ *
+ * - LOCK IN SHARE MODE
+ * - STRAIGHT_JOIN
+ * - HIGH_PRIORITY
+ * - SQL_BIG_RESULT
+ * - SQL_BUFFER_RESULT
+ * - SQL_SMALL_RESULT
+ * - SQL_CALC_FOUND_ROWS
+ * - SQL_CACHE
+ * - SQL_NO_CACHE
+ *
+ *
+ * @param string|array $join_conds
+ *
+ * Optional associative array of table-specific join conditions. In the
+ * most common case, this is unnecessary, since the join condition can be
+ * in $conds. However, it is useful for doing a LEFT JOIN.
+ *
+ * The key of the array contains the table name or alias. The value is an
+ * array with two elements, numbered 0 and 1. The first gives the type of
+ * join, the second is the same as the $conds parameter. Thus it can be
+ * an SQL fragment, or an array where the string keys are equality and the
+ * numeric keys are SQL fragments all AND'd together. For example:
+ *
+ * [ 'page' => [ 'LEFT JOIN', 'page_latest=rev_id' ] ]
+ *
+ * @return ResultWrapper|bool If the query returned no rows, a ResultWrapper
+ * with no rows in it will be returned. If there was a query error, a
+ * DBQueryError exception will be thrown, except if the "ignore errors"
+ * option was set, in which case false will be returned.
+ */
+ public function select(
+ $table, $vars, $conds = '', $fname = __METHOD__,
+ $options = [], $join_conds = []
+ );
+
+ /**
+ * The equivalent of IDatabase::select() except that the constructed SQL
+ * is returned, instead of being immediately executed. This can be useful for
+ * doing UNION queries, where the SQL text of each query is needed. In general,
+ * however, callers outside of Database classes should just use select().
+ *
+ * @param string|array $table Table name
+ * @param string|array $vars Field names
+ * @param string|array $conds Conditions
+ * @param string $fname Caller function name
+ * @param string|array $options Query options
+ * @param string|array $join_conds Join conditions
+ *
+ * @return string SQL query string.
+ * @see IDatabase::select()
+ */
+ public function selectSQLText(
+ $table, $vars, $conds = '', $fname = __METHOD__,
+ $options = [], $join_conds = []
+ );
+
+ /**
+ * Single row SELECT wrapper. Equivalent to IDatabase::select(), except
+ * that a single row object is returned. If the query returns no rows,
+ * false is returned.
+ *
+ * @param string|array $table Table name
+ * @param string|array $vars Field names
+ * @param array $conds Conditions
+ * @param string $fname Caller function name
+ * @param string|array $options Query options
+ * @param array|string $join_conds Join conditions
+ *
+ * @return stdClass|bool
+ */
+ public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
+ $options = [], $join_conds = []
+ );
+
+ /**
+ * Estimate the number of rows in dataset
+ *
+ * MySQL allows you to estimate the number of rows that would be returned
+ * by a SELECT query, using EXPLAIN SELECT. The estimate is provided using
+ * index cardinality statistics, and is notoriously inaccurate, especially
+ * when large numbers of rows have recently been added or deleted.
+ *
+ * For DBMSs that don't support fast result size estimation, this function
+ * will actually perform the SELECT COUNT(*).
+ *
+ * Takes the same arguments as IDatabase::select().
+ *
+ * @param string $table Table name
+ * @param string $vars Unused
+ * @param array|string $conds Filters on the table
+ * @param string $fname Function name for profiling
+ * @param array $options Options for select
+ * @return int Row count
+ */
+ public function estimateRowCount(
+ $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = []
+ );
+
+ /**
+ * Get the number of rows in dataset
+ *
+ * This is useful when trying to do COUNT(*) but with a LIMIT for performance.
+ *
+ * Takes the same arguments as IDatabase::select().
+ *
+ * @since 1.27 Added $join_conds parameter
+ *
+ * @param array|string $tables Table names
+ * @param string $vars Unused
+ * @param array|string $conds Filters on the table
+ * @param string $fname Function name for profiling
+ * @param array $options Options for select
+ * @param array $join_conds Join conditions (since 1.27)
+ * @return int Row count
+ */
+ public function selectRowCount(
+ $tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
+ );
+
+ /**
+ * Determines whether a field exists in a table
+ *
+ * @param string $table Table name
+ * @param string $field Filed to check on that table
+ * @param string $fname Calling function name (optional)
+ * @return bool Whether $table has filed $field
+ */
+ public function fieldExists( $table, $field, $fname = __METHOD__ );
+
+ /**
+ * Determines whether an index exists
+ * Usually throws a DBQueryError on failure
+ * If errors are explicitly ignored, returns NULL on failure
+ *
+ * @param string $table
+ * @param string $index
+ * @param string $fname
+ * @return bool|null
+ */
+ public function indexExists( $table, $index, $fname = __METHOD__ );
+
+ /**
+ * Query whether a given table exists
+ *
+ * @param string $table
+ * @param string $fname
+ * @return bool
+ */
+ public function tableExists( $table, $fname = __METHOD__ );
+
+ /**
+ * Determines if a given index is unique
+ *
+ * @param string $table
+ * @param string $index
+ *
+ * @return bool
+ */
+ public function indexUnique( $table, $index );
+
+ /**
+ * INSERT wrapper, inserts an array into a table.
+ *
+ * $a may be either:
+ *
+ * - A single associative array. The array keys are the field names, and
+ * the values are the values to insert. The values are treated as data
+ * and will be quoted appropriately. If NULL is inserted, this will be
+ * converted to a database NULL.
+ * - An array with numeric keys, holding a list of associative arrays.
+ * This causes a multi-row INSERT on DBMSs that support it. The keys in
+ * each subarray must be identical to each other, and in the same order.
+ *
+ * Usually throws a DBQueryError on failure. If errors are explicitly ignored,
+ * returns success.
+ *
+ * $options is an array of options, with boolean options encoded as values
+ * with numeric keys, in the same style as $options in
+ * IDatabase::select(). Supported options are:
+ *
+ * - IGNORE: Boolean: if present, duplicate key errors are ignored, and
+ * any rows which cause duplicate key errors are not inserted. It's
+ * possible to determine how many rows were successfully inserted using
+ * IDatabase::affectedRows().
+ *
+ * @param string $table Table name. This will be passed through
+ * DatabaseBase::tableName().
+ * @param array $a Array of rows to insert
+ * @param string $fname Calling function name (use __METHOD__) for logs/profiling
+ * @param array $options Array of options
+ *
+ * @return bool
+ */
+ public function insert( $table, $a, $fname = __METHOD__, $options = [] );
+
+ /**
+ * UPDATE wrapper. Takes a condition array and a SET array.
+ *
+ * @param string $table Name of the table to UPDATE. This will be passed through
+ * DatabaseBase::tableName().
+ * @param array $values An array of values to SET. For each array element,
+ * the key gives the field name, and the value gives the data to set
+ * that field to. The data will be quoted by IDatabase::addQuotes().
+ * @param array $conds An array of conditions (WHERE). See
+ * IDatabase::select() for the details of the format of condition
+ * arrays. Use '*' to update all rows.
+ * @param string $fname The function name of the caller (from __METHOD__),
+ * for logging and profiling.
+ * @param array $options An array of UPDATE options, can be:
+ * - IGNORE: Ignore unique key conflicts
+ * - LOW_PRIORITY: MySQL-specific, see MySQL manual.
+ * @return bool
+ */
+ public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] );
+
+ /**
+ * Makes an encoded list of strings from an array
+ *
+ * @param array $a Containing the data
+ * @param int $mode Constant
+ * - LIST_COMMA: Comma separated, no field names
+ * - LIST_AND: ANDed WHERE clause (without the WHERE). See the
+ * documentation for $conds in IDatabase::select().
+ * - LIST_OR: ORed WHERE clause (without the WHERE)
+ * - LIST_SET: Comma separated with field names, like a SET clause
+ * - LIST_NAMES: Comma separated field names
+ * @throws DBError
+ * @return string
+ */
+ public function makeList( $a, $mode = LIST_COMMA );
+
+ /**
+ * Build a partial where clause from a 2-d array such as used for LinkBatch.
+ * The keys on each level may be either integers or strings.
+ *
+ * @param array $data Organized as 2-d
+ * [ baseKeyVal => [ subKeyVal => [ignored], ... ], ... ]
+ * @param string $baseKey Field name to match the base-level keys to (eg 'pl_namespace')
+ * @param string $subKey Field name to match the sub-level keys to (eg 'pl_title')
+ * @return string|bool SQL fragment, or false if no items in array
+ */
+ public function makeWhereFrom2d( $data, $baseKey, $subKey );
+
+ /**
+ * @param string $field
+ * @return string
+ */
+ public function bitNot( $field );
+
+ /**
+ * @param string $fieldLeft
+ * @param string $fieldRight
+ * @return string
+ */
+ public function bitAnd( $fieldLeft, $fieldRight );
+
+ /**
+ * @param string $fieldLeft
+ * @param string $fieldRight
+ * @return string
+ */
+ public function bitOr( $fieldLeft, $fieldRight );
+
+ /**
+ * Build a concatenation list to feed into a SQL query
+ * @param array $stringList List of raw SQL expressions; caller is
+ * responsible for any quoting
+ * @return string
+ */
+ public function buildConcat( $stringList );
+
+ /**
+ * Build a GROUP_CONCAT or equivalent statement for a query.
+ *
+ * This is useful for combining a field for several rows into a single string.
+ * NULL values will not appear in the output, duplicated values will appear,
+ * and the resulting delimiter-separated values have no defined sort order.
+ * Code using the results may need to use the PHP unique() or sort() methods.
+ *
+ * @param string $delim Glue to bind the results together
+ * @param string|array $table Table name
+ * @param string $field Field name
+ * @param string|array $conds Conditions
+ * @param string|array $join_conds Join conditions
+ * @return string SQL text
+ * @since 1.23
+ */
+ public function buildGroupConcatField(
+ $delim, $table, $field, $conds = '', $join_conds = []
+ );
+
+ /**
+ * Change the current database
+ *
+ * @param string $db
+ * @return bool Success or failure
+ */
+ public function selectDB( $db );
+
+ /**
+ * Get the current DB name
+ * @return string
+ */
+ public function getDBname();
+
+ /**
+ * Get the server hostname or IP address
+ * @return string
+ */
+ public function getServer();
+
+ /**
+ * Adds quotes and backslashes.
+ *
+ * @param string|Blob $s
+ * @return string
+ */
+ public function addQuotes( $s );
+
+ /**
+ * LIKE statement wrapper, receives a variable-length argument list with
+ * parts of pattern to match containing either string literals that will be
+ * escaped or tokens returned by anyChar() or anyString(). Alternatively,
+ * the function could be provided with an array of aforementioned
+ * parameters.
+ *
+ * Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns
+ * a LIKE clause that searches for subpages of 'My page title'.
+ * Alternatively:
+ * $pattern = [ 'My_page_title/', $dbr->anyString() ];
+ * $query .= $dbr->buildLike( $pattern );
+ *
+ * @since 1.16
+ * @return string Fully built LIKE statement
+ */
+ public function buildLike();
+
+ /**
+ * Returns a token for buildLike() that denotes a '_' to be used in a LIKE query
+ *
+ * @return LikeMatch
+ */
+ public function anyChar();
+
+ /**
+ * Returns a token for buildLike() that denotes a '%' to be used in a LIKE query
+ *
+ * @return LikeMatch
+ */
+ public function anyString();
+
+ /**
+ * Returns an appropriately quoted sequence value for inserting a new row.
+ * MySQL has autoincrement fields, so this is just NULL. But the PostgreSQL
+ * subclass will return an integer, and save the value for insertId()
+ *
+ * Any implementation of this function should *not* involve reusing
+ * sequence numbers created for rolled-back transactions.
+ * See http://bugs.mysql.com/bug.php?id=30767 for details.
+ * @param string $seqName
+ * @return null|int
+ */
+ public function nextSequenceValue( $seqName );
+
+ /**
+ * REPLACE query wrapper.
+ *
+ * REPLACE is a very handy MySQL extension, which functions like an INSERT
+ * except that when there is a duplicate key error, the old row is deleted
+ * and the new row is inserted in its place.
+ *
+ * We simulate this with standard SQL with a DELETE followed by INSERT. To
+ * perform the delete, we need to know what the unique indexes are so that
+ * we know how to find the conflicting rows.
+ *
+ * It may be more efficient to leave off unique indexes which are unlikely
+ * to collide. However if you do this, you run the risk of encountering
+ * errors which wouldn't have occurred in MySQL.
+ *
+ * @param string $table The table to replace the row(s) in.
+ * @param array $uniqueIndexes Is an array of indexes. Each element may be either
+ * a field name or an array of field names
+ * @param array $rows Can be either a single row to insert, or multiple rows,
+ * in the same format as for IDatabase::insert()
+ * @param string $fname Calling function name (use __METHOD__) for logs/profiling
+ */
+ public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ );
+
+ /**
+ * INSERT ON DUPLICATE KEY UPDATE wrapper, upserts an array into a table.
+ *
+ * This updates any conflicting rows (according to the unique indexes) using
+ * the provided SET clause and inserts any remaining (non-conflicted) rows.
+ *
+ * $rows may be either:
+ * - A single associative array. The array keys are the field names, and
+ * the values are the values to insert. The values are treated as data
+ * and will be quoted appropriately. If NULL is inserted, this will be
+ * converted to a database NULL.
+ * - An array with numeric keys, holding a list of associative arrays.
+ * This causes a multi-row INSERT on DBMSs that support it. The keys in
+ * each subarray must be identical to each other, and in the same order.
+ *
+ * It may be more efficient to leave off unique indexes which are unlikely
+ * to collide. However if you do this, you run the risk of encountering
+ * errors which wouldn't have occurred in MySQL.
+ *
+ * Usually throws a DBQueryError on failure. If errors are explicitly ignored,
+ * returns success.
+ *
+ * @since 1.22
+ *
+ * @param string $table Table name. This will be passed through DatabaseBase::tableName().
+ * @param array $rows A single row or list of rows to insert
+ * @param array $uniqueIndexes List of single field names or field name tuples
+ * @param array $set An array of values to SET. For each array element, the
+ * key gives the field name, and the value gives the data to set that
+ * field to. The data will be quoted by IDatabase::addQuotes().
+ * @param string $fname Calling function name (use __METHOD__) for logs/profiling
+ * @throws Exception
+ * @return bool
+ */
+ public function upsert(
+ $table, array $rows, array $uniqueIndexes, array $set, $fname = __METHOD__
+ );
+
+ /**
+ * DELETE where the condition is a join.
+ *
+ * MySQL overrides this to use a multi-table DELETE syntax, in other databases
+ * we use sub-selects
+ *
+ * For safety, an empty $conds will not delete everything. If you want to
+ * delete all rows where the join condition matches, set $conds='*'.
+ *
+ * DO NOT put the join condition in $conds.
+ *
+ * @param string $delTable The table to delete from.
+ * @param string $joinTable The other table.
+ * @param string $delVar The variable to join on, in the first table.
+ * @param string $joinVar The variable to join on, in the second table.
+ * @param array $conds Condition array of field names mapped to variables,
+ * ANDed together in the WHERE clause
+ * @param string $fname Calling function name (use __METHOD__) for logs/profiling
+ * @throws DBUnexpectedError
+ */
+ public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
+ $fname = __METHOD__
+ );
+
+ /**
+ * DELETE query wrapper.
+ *
+ * @param array $table Table name
+ * @param string|array $conds Array of conditions. See $conds in IDatabase::select()
+ * for the format. Use $conds == "*" to delete all rows
+ * @param string $fname Name of the calling function
+ * @throws DBUnexpectedError
+ * @return bool|ResultWrapper
+ */
+ public function delete( $table, $conds, $fname = __METHOD__ );
+
+ /**
+ * INSERT SELECT wrapper. Takes data from a SELECT query and inserts it
+ * into another table.
+ *
+ * @param string $destTable The table name to insert into
+ * @param string|array $srcTable May be either a table name, or an array of table names
+ * to include in a join.
+ *
+ * @param array $varMap Must be an associative array of the form
+ * [ 'dest1' => 'source1', ... ]. Source items may be literals
+ * rather than field names, but strings should be quoted with
+ * IDatabase::addQuotes()
+ *
+ * @param array $conds Condition array. See $conds in IDatabase::select() for
+ * the details of the format of condition arrays. May be "*" to copy the
+ * whole table.
+ *
+ * @param string $fname The function name of the caller, from __METHOD__
+ *
+ * @param array $insertOptions Options for the INSERT part of the query, see
+ * IDatabase::insert() for details.
+ * @param array $selectOptions Options for the SELECT part of the query, see
+ * IDatabase::select() for details.
+ *
+ * @return ResultWrapper
+ */
+ public function insertSelect( $destTable, $srcTable, $varMap, $conds,
+ $fname = __METHOD__,
+ $insertOptions = [], $selectOptions = []
+ );
+
+ /**
+ * Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries
+ * within the UNION construct.
+ * @return bool
+ */
+ public function unionSupportsOrderAndLimit();
+
+ /**
+ * Construct a UNION query
+ * This is used for providing overload point for other DB abstractions
+ * not compatible with the MySQL syntax.
+ * @param array $sqls SQL statements to combine
+ * @param bool $all Use UNION ALL
+ * @return string SQL fragment
+ */
+ public function unionQueries( $sqls, $all );
+
+ /**
+ * Returns an SQL expression for a simple conditional. This doesn't need
+ * to be overridden unless CASE isn't supported in your DBMS.
+ *
+ * @param string|array $cond SQL expression which will result in a boolean value
+ * @param string $trueVal SQL expression to return if true
+ * @param string $falseVal SQL expression to return if false
+ * @return string SQL fragment
+ */
+ public function conditional( $cond, $trueVal, $falseVal );
+
+ /**
+ * Returns a comand for str_replace function in SQL query.
+ * Uses REPLACE() in MySQL
+ *
+ * @param string $orig Column to modify
+ * @param string $old Column to seek
+ * @param string $new Column to replace with
+ *
+ * @return string
+ */
+ public function strreplace( $orig, $old, $new );
+
+ /**
+ * Determines how long the server has been up
+ *
+ * @return int
+ */
+ public function getServerUptime();
+
+ /**
+ * Determines if the last failure was due to a deadlock
+ *
+ * @return bool
+ */
+ public function wasDeadlock();
+
+ /**
+ * Determines if the last failure was due to a lock timeout
+ *
+ * @return bool
+ */
+ public function wasLockTimeout();
+
+ /**
+ * Determines if the last query error was due to a dropped connection and should
+ * be dealt with by pinging the connection and reissuing the query.
+ *
+ * @return bool
+ */
+ public function wasErrorReissuable();
+
+ /**
+ * Determines if the last failure was due to the database being read-only.
+ *
+ * @return bool
+ */
+ public function wasReadOnlyError();
+
+ /**
+ * Wait for the replica DB to catch up to a given master position
+ *
+ * @param DBMasterPos $pos
+ * @param int $timeout The maximum number of seconds to wait for synchronisation
+ * @return int|null Zero if the replica DB was past that position already,
+ * greater than zero if we waited for some period of time, less than
+ * zero if it timed out, and null on error
+ */
+ public function masterPosWait( DBMasterPos $pos, $timeout );
+
+ /**
+ * Get the replication position of this replica DB
+ *
+ * @return DBMasterPos|bool False if this is not a replica DB.
+ */
+ public function getSlavePos();
+
+ /**
+ * Get the position of this master
+ *
+ * @return DBMasterPos|bool False if this is not a master
+ */
+ public function getMasterPos();
+
+ /**
+ * @return bool Whether the DB is marked as read-only server-side
+ * @since 1.28
+ */
+ public function serverIsReadOnly();
+
+ /**
+ * Run a callback as soon as the current transaction commits or rolls back.
+ * An error is thrown if no transaction is pending. Queries in the function will run in
+ * AUTO-COMMIT mode unless there are begin() calls. Callbacks must commit any transactions
+ * that they begin.
+ *
+ * This is useful for combining cooperative locks and DB transactions.
+ *
+ * The callback takes one argument:
+ * - How the transaction ended (IDatabase::TRIGGER_COMMIT or IDatabase::TRIGGER_ROLLBACK)
+ *
+ * @param callable $callback
+ * @return mixed
+ * @since 1.28
+ */
+ public function onTransactionResolution( callable $callback );
+
+ /**
+ * Run a callback as soon as there is no transaction pending.
+ * If there is a transaction and it is rolled back, then the callback is cancelled.
+ * Queries in the function will run in AUTO-COMMIT mode unless there are begin() calls.
+ * Callbacks must commit any transactions that they begin.
+ *
+ * This is useful for updates to different systems or when separate transactions are needed.
+ * For example, one might want to enqueue jobs into a system outside the database, but only
+ * after the database is updated so that the jobs will see the data when they actually run.
+ * It can also be used for updates that easily cause deadlocks if locks are held too long.
+ *
+ * Updates will execute in the order they were enqueued.
+ *
+ * The callback takes one argument:
+ * - How the transaction ended (IDatabase::TRIGGER_COMMIT or IDatabase::TRIGGER_IDLE)
+ *
+ * @param callable $callback
+ * @since 1.20
+ */
+ public function onTransactionIdle( callable $callback );
+
+ /**
+ * Run a callback before the current transaction commits or now if there is none.
+ * If there is a transaction and it is rolled back, then the callback is cancelled.
+ * Callbacks must not start nor commit any transactions. If no transaction is active,
+ * then a transaction will wrap the callback.
+ *
+ * This is useful for updates that easily cause deadlocks if locks are held too long
+ * but where atomicity is strongly desired for these updates and some related updates.
+ *
+ * Updates will execute in the order they were enqueued.
+ *
+ * @param callable $callback
+ * @since 1.22
+ */
+ public function onTransactionPreCommitOrIdle( callable $callback );
+
+ /**
+ * Run a callback each time any transaction commits or rolls back
+ *
+ * The callback takes two arguments:
+ * - IDatabase::TRIGGER_COMMIT or IDatabase::TRIGGER_ROLLBACK
+ * - This IDatabase object
+ * Callbacks must commit any transactions that they begin.
+ *
+ * Registering a callback here will not affect writesOrCallbacks() pending
+ *
+ * @param string $name Callback name
+ * @param callable|null $callback Use null to unset a listener
+ * @return mixed
+ * @since 1.28
+ */
+ public function setTransactionListener( $name, callable $callback = null );
+
+ /**
+ * Begin an atomic section of statements
+ *
+ * If a transaction has been started already, just keep track of the given
+ * section name to make sure the transaction is not committed pre-maturely.
+ * This function can be used in layers (with sub-sections), so use a stack
+ * to keep track of the different atomic sections. If there is no transaction,
+ * start one implicitly.
+ *
+ * The goal of this function is to create an atomic section of SQL queries
+ * without having to start a new transaction if it already exists.
+ *
+ * Atomic sections are more strict than transactions. With transactions,
+ * attempting to begin a new transaction when one is already running results
+ * in MediaWiki issuing a brief warning and doing an implicit commit. All
+ * atomic levels *must* be explicitly closed using IDatabase::endAtomic(),
+ * and any database transactions cannot be began or committed until all atomic
+ * levels are closed. There is no such thing as implicitly opening or closing
+ * an atomic section.
+ *
+ * @since 1.23
+ * @param string $fname
+ * @throws DBError
+ */
+ public function startAtomic( $fname = __METHOD__ );
+
+ /**
+ * Ends an atomic section of SQL statements
+ *
+ * Ends the next section of atomic SQL statements and commits the transaction
+ * if necessary.
+ *
+ * @since 1.23
+ * @see IDatabase::startAtomic
+ * @param string $fname
+ * @throws DBError
+ */
+ public function endAtomic( $fname = __METHOD__ );
+
+ /**
+ * Run a callback to do an atomic set of updates for this database
+ *
+ * The $callback takes the following arguments:
+ * - This database object
+ * - The value of $fname
+ *
+ * If any exception occurs in the callback, then rollback() will be called and the error will
+ * be re-thrown. It may also be that the rollback itself fails with an exception before then.
+ * In any case, such errors are expected to terminate the request, without any outside caller
+ * attempting to catch errors and commit anyway. Note that any rollback undoes all prior
+ * atomic section and uncommitted updates, which trashes the current request, requiring an
+ * error to be displayed.
+ *
+ * This can be an alternative to explicit startAtomic()/endAtomic() calls.
+ *
+ * @see DatabaseBase::startAtomic
+ * @see DatabaseBase::endAtomic
+ *
+ * @param string $fname Caller name (usually __METHOD__)
+ * @param callable $callback Callback that issues DB updates
+ * @return mixed $res Result of the callback (since 1.28)
+ * @throws DBError
+ * @throws RuntimeException
+ * @throws UnexpectedValueException
+ * @since 1.27
+ */
+ public function doAtomicSection( $fname, callable $callback );
+
+ /**
+ * Begin a transaction. If a transaction is already in progress,
+ * that transaction will be committed before the new transaction is started.
+ *
+ * Only call this from code with outer transcation scope.
+ * See https://www.mediawiki.org/wiki/Database_transactions for details.
+ * Nesting of transactions is not supported.
+ *
+ * Note that when the DBO_TRX flag is set (which is usually the case for web
+ * requests, but not for maintenance scripts), any previous database query
+ * will have started a transaction automatically.
+ *
+ * Nesting of transactions is not supported. Attempts to nest transactions
+ * will cause a warning, unless the current transaction was started
+ * automatically because of the DBO_TRX flag.
+ *
+ * @param string $fname Calling function name
+ * @param string $mode A situationally valid IDatabase::TRANSACTION_* constant [optional]
+ * @throws DBError
+ */
+ public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT );
+
+ /**
+ * Commits a transaction previously started using begin().
+ * If no transaction is in progress, a warning is issued.
+ *
+ * Only call this from code with outer transcation scope.
+ * See https://www.mediawiki.org/wiki/Database_transactions for details.
+ * Nesting of transactions is not supported.
+ *
+ * @param string $fname
+ * @param string $flush Flush flag, set to situationally valid IDatabase::FLUSHING_*
+ * constant to disable warnings about explicitly committing implicit transactions,
+ * or calling commit when no transaction is in progress.
+ *
+ * This will trigger an exception if there is an ongoing explicit transaction.
+ *
+ * Only set the flush flag if you are sure that these warnings are not applicable,
+ * and no explicit transactions are open.
+ *
+ * @throws DBUnexpectedError
+ */
+ public function commit( $fname = __METHOD__, $flush = '' );
+
+ /**
+ * Rollback a transaction previously started using begin().
+ * If no transaction is in progress, a warning is issued.
+ *
+ * Only call this from code with outer transcation scope.
+ * See https://www.mediawiki.org/wiki/Database_transactions for details.
+ * Nesting of transactions is not supported. If a serious unexpected error occurs,
+ * throwing an Exception is preferrable, using a pre-installed error handler to trigger
+ * rollback (in any case, failure to issue COMMIT will cause rollback server-side).
+ *
+ * @param string $fname Calling function name
+ * @param string $flush Flush flag, set to a situationally valid IDatabase::FLUSHING_*
+ * constant to disable warnings about calling rollback when no transaction is in
+ * progress. This will silently break any ongoing explicit transaction. Only set the
+ * flush flag if you are sure that it is safe to ignore these warnings in your context.
+ * @throws DBUnexpectedError
+ * @since 1.23 Added $flush parameter
+ */
+ public function rollback( $fname = __METHOD__, $flush = '' );
+
+ /**
+ * Commit any transaction but error out if writes or callbacks are pending
+ *
+ * This is intended for clearing out REPEATABLE-READ snapshots so that callers can
+ * see a new point-in-time of the database. This is useful when one of many transaction
+ * rounds finished and significant time will pass in the script's lifetime. It is also
+ * useful to call on a replica DB after waiting on replication to catch up to the master.
+ *
+ * @param string $fname Calling function name
+ * @throws DBUnexpectedError
+ * @since 1.28
+ */
+ public function flushSnapshot( $fname = __METHOD__ );
+
+ /**
+ * List all tables on the database
+ *
+ * @param string $prefix Only show tables with this prefix, e.g. mw_
+ * @param string $fname Calling function name
+ * @throws DBError
+ * @return array
+ */
+ public function listTables( $prefix = null, $fname = __METHOD__ );
+
+ /**
+ * Convert a timestamp in one of the formats accepted by wfTimestamp()
+ * to the format used for inserting into timestamp fields in this DBMS.
+ *
+ * The result is unquoted, and needs to be passed through addQuotes()
+ * before it can be included in raw SQL.
+ *
+ * @param string|int $ts
+ *
+ * @return string
+ */
+ public function timestamp( $ts = 0 );
+
+ /**
+ * Convert a timestamp in one of the formats accepted by wfTimestamp()
+ * to the format used for inserting into timestamp fields in this DBMS. If
+ * NULL is input, it is passed through, allowing NULL values to be inserted
+ * into timestamp fields.
+ *
+ * The result is unquoted, and needs to be passed through addQuotes()
+ * before it can be included in raw SQL.
+ *
+ * @param string|int $ts
+ *
+ * @return string
+ */
+ public function timestampOrNull( $ts = null );
+
+ /**
+ * Ping the server and try to reconnect if it there is no connection
+ *
+ * @param float|null &$rtt Value to store the estimated RTT [optional]
+ * @return bool Success or failure
+ */
+ public function ping( &$rtt = null );
+
+ /**
+ * Get replica DB lag. Currently supported only by MySQL.
+ *
+ * Note that this function will generate a fatal error on many
+ * installations. Most callers should use LoadBalancer::safeGetLag()
+ * instead.
+ *
+ * @return int|bool Database replication lag in seconds or false on error
+ */
+ public function getLag();
+
+ /**
+ * Get the replica DB lag when the current transaction started
+ * or a general lag estimate if not transaction is active
+ *
+ * This is useful when transactions might use snapshot isolation
+ * (e.g. REPEATABLE-READ in innodb), so the "real" lag of that data
+ * is this lag plus transaction duration. If they don't, it is still
+ * safe to be pessimistic. In AUTO-COMMIT mode, this still gives an
+ * indication of the staleness of subsequent reads.
+ *
+ * @return array ('lag': seconds or false on error, 'since': UNIX timestamp of BEGIN)
+ * @since 1.27
+ */
+ public function getSessionLagStatus();
+
+ /**
+ * Return the maximum number of items allowed in a list, or 0 for unlimited.
+ *
+ * @return int
+ */
+ public function maxListLen();
+
+ /**
+ * Some DBMSs have a special format for inserting into blob fields, they
+ * don't allow simple quoted strings to be inserted. To insert into such
+ * a field, pass the data through this function before passing it to
+ * IDatabase::insert().
+ *
+ * @param string $b
+ * @return string
+ */
+ public function encodeBlob( $b );
+
+ /**
+ * Some DBMSs return a special placeholder object representing blob fields
+ * in result objects. Pass the object through this function to return the
+ * original string.
+ *
+ * @param string|Blob $b
+ * @return string
+ */
+ public function decodeBlob( $b );
+
+ /**
+ * Override database's default behavior. $options include:
+ * 'connTimeout' : Set the connection timeout value in seconds.
+ * May be useful for very long batch queries such as
+ * full-wiki dumps, where a single query reads out over
+ * hours or days.
+ *
+ * @param array $options
+ * @return void
+ */
+ public function setSessionOptions( array $options );
+
+ /**
+ * Set variables to be used in sourceFile/sourceStream, in preference to the
+ * ones in $GLOBALS. If an array is set here, $GLOBALS will not be used at
+ * all. If it's set to false, $GLOBALS will be used.
+ *
+ * @param bool|array $vars Mapping variable name to value.
+ */
+ public function setSchemaVars( $vars );
+
+ /**
+ * Check to see if a named lock is available (non-blocking)
+ *
+ * @param string $lockName Name of lock to poll
+ * @param string $method Name of method calling us
+ * @return bool
+ * @since 1.20
+ */
+ public function lockIsFree( $lockName, $method );
+
+ /**
+ * Acquire a named lock
+ *
+ * Named locks are not related to transactions
+ *
+ * @param string $lockName Name of lock to aquire
+ * @param string $method Name of the calling method
+ * @param int $timeout Acquisition timeout in seconds
+ * @return bool
+ */
+ public function lock( $lockName, $method, $timeout = 5 );
+
+ /**
+ * Release a lock
+ *
+ * Named locks are not related to transactions
+ *
+ * @param string $lockName Name of lock to release
+ * @param string $method Name of the calling method
+ *
+ * @return int Returns 1 if the lock was released, 0 if the lock was not established
+ * by this thread (in which case the lock is not released), and NULL if the named
+ * lock did not exist
+ */
+ public function unlock( $lockName, $method );
+
+ /**
+ * Acquire a named lock, flush any transaction, and return an RAII style unlocker object
+ *
+ * Only call this from outer transcation scope and when only one DB will be affected.
+ * See https://www.mediawiki.org/wiki/Database_transactions for details.
+ *
+ * This is suitiable for transactions that need to be serialized using cooperative locks,
+ * where each transaction can see each others' changes. Any transaction is flushed to clear
+ * out stale REPEATABLE-READ snapshot data. Once the returned object falls out of PHP scope,
+ * the lock will be released unless a transaction is active. If one is active, then the lock
+ * will be released when it either commits or rolls back.
+ *
+ * If the lock acquisition failed, then no transaction flush happens, and null is returned.
+ *
+ * @param string $lockKey Name of lock to release
+ * @param string $fname Name of the calling method
+ * @param int $timeout Acquisition timeout in seconds
+ * @return ScopedCallback|null
+ * @throws DBUnexpectedError
+ * @since 1.27
+ */
+ public function getScopedLockAndFlush( $lockKey, $fname, $timeout );
+
+ /**
+ * Check to see if a named lock used by lock() use blocking queues
+ *
+ * @return bool
+ * @since 1.26
+ */
+ public function namedLocksEnqueue();
+
+ /**
+ * Find out when 'infinity' is. Most DBMSes support this. This is a special
+ * keyword for timestamps in PostgreSQL, and works with CHAR(14) as well
+ * because "i" sorts after all numbers.
+ *
+ * @return string
+ */
+ public function getInfinity();
+
+ /**
+ * Encode an expiry time into the DBMS dependent format
+ *
+ * @param string $expiry Timestamp for expiry, or the 'infinity' string
+ * @return string
+ */
+ public function encodeExpiry( $expiry );
+
+ /**
+ * Decode an expiry time into a DBMS independent format
+ *
+ * @param string $expiry DB timestamp field value for expiry
+ * @param int $format TS_* constant, defaults to TS_MW
+ * @return string
+ */
+ public function decodeExpiry( $expiry, $format = TS_MW );
+
+ /**
+ * Allow or deny "big selects" for this session only. This is done by setting
+ * the sql_big_selects session variable.
+ *
+ * This is a MySQL-specific feature.
+ *
+ * @param bool|string $value True for allow, false for deny, or "default" to
+ * restore the initial value
+ */
+ public function setBigSelects( $value = true );
+
+ /**
+ * @return bool Whether this DB is read-only
+ * @since 1.27
+ */
+ public function isReadOnly();
+}
--- /dev/null
+<?php
+/**
+ * An object representing a master or replica DB position in a replicated setup.
+ *
+ * The implementation details of this opaque type are up to the database subclass.
+ */
+interface DBMasterPos {
+ /**
+ * @return float UNIX timestamp
+ * @since 1.25
+ */
+ public function asOfTime();
+
+ /**
+ * @param DBMasterPos $pos
+ * @return bool Whether this position is at or higher than $pos
+ * @since 1.27
+ */
+ public function hasReached( DBMasterPos $pos );
+
+ /**
+ * @param DBMasterPos $pos
+ * @return bool Whether this position appears to be for the same channel as another
+ * @since 1.27
+ */
+ public function channelsMatch( DBMasterPos $pos );
+
+ /**
+ * @return string
+ * @since 1.27
+ */
+ public function __toString();
+}
--- /dev/null
+<?php
+/**
+ * DBMasterPos class for MySQL/MariaDB
+ *
+ * Note that master positions and sync logic here make some assumptions:
+ * - Binlog-based usage assumes single-source replication and non-hierarchical replication.
+ * - GTID-based usage allows getting/syncing with multi-source replication. It is assumed
+ * that GTID sets are complete (e.g. include all domains on the server).
+ */
+class MySQLMasterPos implements DBMasterPos {
+ /** @var string Binlog file */
+ public $file;
+ /** @var int Binglog file position */
+ public $pos;
+ /** @var string[] GTID list */
+ public $gtids = [];
+ /** @var float UNIX timestamp */
+ public $asOfTime = 0.0;
+
+ /**
+ * @param string $file Binlog file name
+ * @param integer $pos Binlog position
+ * @param string $gtid Comma separated GTID set [optional]
+ */
+ function __construct( $file, $pos, $gtid = '' ) {
+ $this->file = $file;
+ $this->pos = $pos;
+ $this->gtids = array_map( 'trim', explode( ',', $gtid ) );
+ $this->asOfTime = microtime( true );
+ }
+
+ /**
+ * @return string <binlog file>/<position>, e.g db1034-bin.000976/843431247
+ */
+ function __toString() {
+ return "{$this->file}/{$this->pos}";
+ }
+
+ function asOfTime() {
+ return $this->asOfTime;
+ }
+
+ function hasReached( DBMasterPos $pos ) {
+ if ( !( $pos instanceof self ) ) {
+ throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
+ }
+
+ // Prefer GTID comparisons, which work with multi-tier replication
+ $thisPosByDomain = $this->getGtidCoordinates();
+ $thatPosByDomain = $pos->getGtidCoordinates();
+ if ( $thisPosByDomain && $thatPosByDomain ) {
+ $reached = true;
+ // Check that this has positions GTE all of those in $pos for all domains in $pos
+ foreach ( $thatPosByDomain as $domain => $thatPos ) {
+ $thisPos = isset( $thisPosByDomain[$domain] ) ? $thisPosByDomain[$domain] : -1;
+ $reached = $reached && ( $thatPos <= $thisPos );
+ }
+
+ return $reached;
+ }
+
+ // Fallback to the binlog file comparisons
+ $thisBinPos = $this->getBinlogCoordinates();
+ $thatBinPos = $pos->getBinlogCoordinates();
+ if ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] ) {
+ return ( $thisBinPos['pos'] >= $thatBinPos['pos'] );
+ }
+
+ // Comparing totally different binlogs does not make sense
+ return false;
+ }
+
+ function channelsMatch( DBMasterPos $pos ) {
+ if ( !( $pos instanceof self ) ) {
+ throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
+ }
+
+ // Prefer GTID comparisons, which work with multi-tier replication
+ $thisPosDomains = array_keys( $this->getGtidCoordinates() );
+ $thatPosDomains = array_keys( $pos->getGtidCoordinates() );
+ if ( $thisPosDomains && $thatPosDomains ) {
+ // Check that this has GTIDs for all domains in $pos
+ return !array_diff( $thatPosDomains, $thisPosDomains );
+ }
+
+ // Fallback to the binlog file comparisons
+ $thisBinPos = $this->getBinlogCoordinates();
+ $thatBinPos = $pos->getBinlogCoordinates();
+
+ return ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] );
+ }
+
+ /**
+ * @note: this returns false for multi-source replication GTID sets
+ * @see https://mariadb.com/kb/en/mariadb/gtid
+ * @see https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-concepts.html
+ * @return array Map of (domain => integer position) or false
+ */
+ protected function getGtidCoordinates() {
+ $gtidInfos = [];
+ foreach ( $this->gtids as $gtid ) {
+ $m = [];
+ // MariaDB style: <domain>-<server id>-<sequence number>
+ if ( preg_match( '!^(\d+)-\d+-(\d+)$!', $gtid, $m ) ) {
+ $gtidInfos[(int)$m[1]] = (int)$m[2];
+ // MySQL style: <UUID domain>:<sequence number>
+ } elseif ( preg_match( '!^(\w{8}-\w{4}-\w{4}-\w{4}-\w{12}):(\d+)$!', $gtid, $m ) ) {
+ $gtidInfos[$m[1]] = (int)$m[2];
+ } else {
+ $gtidInfos = [];
+ break; // unrecognized GTID
+ }
+
+ }
+
+ return $gtidInfos;
+ }
+
+ /**
+ * @see http://dev.mysql.com/doc/refman/5.7/en/show-master-status.html
+ * @see http://dev.mysql.com/doc/refman/5.7/en/show-slave-status.html
+ * @return array|bool (binlog, (integer file number, integer position)) or false
+ */
+ protected function getBinlogCoordinates() {
+ $m = [];
+ if ( preg_match( '!^(.+)\.(\d+)/(\d+)$!', (string)$this, $m ) ) {
+ return [ 'binlog' => $m[1], 'pos' => [ (int)$m[2], (int)$m[3] ] ];
+ }
+
+ return false;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Overloads the relevant methods of the real ResultsWrapper so it
+ * doesn't go anywhere near an actual database.
+ */
+class FakeResultWrapper extends ResultWrapper {
+ /** @var array */
+ public $result = [];
+
+ /** @var null And it's going to stay that way :D */
+ protected $db = null;
+
+ /** @var int */
+ protected $pos = 0;
+
+ /** @var array|stdClass|bool */
+ protected $currentRow = null;
+
+ /**
+ * @param array $array
+ */
+ function __construct( $array ) {
+ $this->result = $array;
+ }
+
+ /**
+ * @return int
+ */
+ function numRows() {
+ return count( $this->result );
+ }
+
+ /**
+ * @return array|bool
+ */
+ function fetchRow() {
+ if ( $this->pos < count( $this->result ) ) {
+ $this->currentRow = $this->result[$this->pos];
+ } else {
+ $this->currentRow = false;
+ }
+ $this->pos++;
+ if ( is_object( $this->currentRow ) ) {
+ return get_object_vars( $this->currentRow );
+ } else {
+ return $this->currentRow;
+ }
+ }
+
+ function seek( $row ) {
+ $this->pos = $row;
+ }
+
+ function free() {
+ }
+
+ /**
+ * Callers want to be able to access fields with $this->fieldName
+ * @return bool|stdClass
+ */
+ function fetchObject() {
+ $this->fetchRow();
+ if ( $this->currentRow ) {
+ return (object)$this->currentRow;
+ } else {
+ return false;
+ }
+ }
+
+ function rewind() {
+ $this->pos = 0;
+ $this->currentRow = null;
+ }
+
+ /**
+ * @return bool|stdClass
+ */
+ function next() {
+ return $this->fetchObject();
+ }
+}
--- /dev/null
+<?php
+class MssqlResultWrapper extends ResultWrapper {
+ private $mSeekTo = null;
+
+ /**
+ * @return stdClass|bool
+ */
+ public function fetchObject() {
+ $res = $this->result;
+
+ if ( $this->mSeekTo !== null ) {
+ $result = sqlsrv_fetch_object( $res, 'stdClass', [],
+ SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
+ $this->mSeekTo = null;
+ } else {
+ $result = sqlsrv_fetch_object( $res );
+ }
+
+ // MediaWiki expects us to return boolean false when there are no more rows instead of null
+ if ( $result === null ) {
+ return false;
+ }
+
+ return $result;
+ }
+
+ /**
+ * @return array|bool
+ */
+ public function fetchRow() {
+ $res = $this->result;
+
+ if ( $this->mSeekTo !== null ) {
+ $result = sqlsrv_fetch_array( $res, SQLSRV_FETCH_BOTH,
+ SQLSRV_SCROLL_ABSOLUTE, $this->mSeekTo );
+ $this->mSeekTo = null;
+ } else {
+ $result = sqlsrv_fetch_array( $res );
+ }
+
+ // MediaWiki expects us to return boolean false when there are no more rows instead of null
+ if ( $result === null ) {
+ return false;
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param int $row
+ * @return bool
+ */
+ public function seek( $row ) {
+ $res = $this->result;
+
+ // check bounds
+ $numRows = $this->db->numRows( $res );
+ $row = intval( $row );
+
+ if ( $numRows === 0 ) {
+ return false;
+ } elseif ( $row < 0 || $row > $numRows - 1 ) {
+ return false;
+ }
+
+ // Unlike MySQL, the seek actually happens on the next access
+ $this->mSeekTo = $row;
+ return true;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Result wrapper for grabbing data queried by someone else
+ * @ingroup Database
+ */
+class ResultWrapper implements Iterator {
+ /** @var resource */
+ public $result;
+
+ /** @var DatabaseBase */
+ protected $db;
+
+ /** @var int */
+ protected $pos = 0;
+
+ /** @var object|null */
+ protected $currentRow = null;
+
+ /**
+ * Create a new result object from a result resource and a Database object
+ *
+ * @param DatabaseBase $database
+ * @param resource|ResultWrapper $result
+ */
+ function __construct( $database, $result ) {
+ $this->db = $database;
+
+ if ( $result instanceof ResultWrapper ) {
+ $this->result = $result->result;
+ } else {
+ $this->result = $result;
+ }
+ }
+
+ /**
+ * Get the number of rows in a result object
+ *
+ * @return int
+ */
+ function numRows() {
+ return $this->db->numRows( $this );
+ }
+
+ /**
+ * Fetch the next row from the given result object, in object form. Fields can be retrieved with
+ * $row->fieldname, with fields acting like member variables. If no more rows are available,
+ * false is returned.
+ *
+ * @return stdClass|bool
+ * @throws DBUnexpectedError Thrown if the database returns an error
+ */
+ function fetchObject() {
+ return $this->db->fetchObject( $this );
+ }
+
+ /**
+ * Fetch the next row from the given result object, in associative array form. Fields are
+ * retrieved with $row['fieldname']. If no more rows are available, false is returned.
+ *
+ * @return array|bool
+ * @throws DBUnexpectedError Thrown if the database returns an error
+ */
+ function fetchRow() {
+ return $this->db->fetchRow( $this );
+ }
+
+ /**
+ * Free a result object
+ */
+ function free() {
+ $this->db->freeResult( $this );
+ unset( $this->result );
+ unset( $this->db );
+ }
+
+ /**
+ * Change the position of the cursor in a result object.
+ * See mysql_data_seek()
+ *
+ * @param int $row
+ */
+ function seek( $row ) {
+ $this->db->dataSeek( $this, $row );
+ }
+
+ /*
+ * ======= Iterator functions =======
+ * Note that using these in combination with the non-iterator functions
+ * above may cause rows to be skipped or repeated.
+ */
+
+ function rewind() {
+ if ( $this->numRows() ) {
+ $this->db->dataSeek( $this, 0 );
+ }
+ $this->pos = 0;
+ $this->currentRow = null;
+ }
+
+ /**
+ * @return stdClass|array|bool
+ */
+ function current() {
+ if ( is_null( $this->currentRow ) ) {
+ $this->next();
+ }
+
+ return $this->currentRow;
+ }
+
+ /**
+ * @return int
+ */
+ function key() {
+ return $this->pos;
+ }
+
+ /**
+ * @return stdClass
+ */
+ function next() {
+ $this->pos++;
+ $this->currentRow = $this->fetchObject();
+
+ return $this->currentRow;
+ }
+
+ /**
+ * @return bool
+ */
+ function valid() {
+ return $this->current() !== false;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Utility class
+ * @ingroup Database
+ *
+ * This allows us to distinguish a blob from a normal string and an array of strings
+ */
+class Blob {
+ /** @var string */
+ protected $mData;
+
+ function __construct( $data ) {
+ $this->mData = $data;
+ }
+
+ function fetch() {
+ return $this->mData;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Used by DatabaseBase::buildLike() to represent characters that have special
+ * meaning in SQL LIKE clauses and thus need no escaping. Don't instantiate it
+ * manually, use DatabaseBase::anyChar() and anyString() instead.
+ */
+class LikeMatch {
+ /** @var string */
+ private $str;
+
+ /**
+ * Store a string into a LikeMatch marker object.
+ *
+ * @param string $s
+ */
+ public function __construct( $s ) {
+ $this->str = $s;
+ }
+
+ /**
+ * Return the original stored string.
+ *
+ * @return string
+ */
+ public function toString() {
+ return $this->str;
+ }
+}
--- /dev/null
+<?php
+class MssqlBlob extends Blob {
+ public function __construct( $data ) {
+ if ( $data instanceof MssqlBlob ) {
+ return $data;
+ } elseif ( $data instanceof Blob ) {
+ $this->mData = $data->fetch();
+ } elseif ( is_array( $data ) && is_object( $data ) ) {
+ $this->mData = serialize( $data );
+ } else {
+ $this->mData = $data;
+ }
+ }
+
+ /**
+ * Returns an unquoted hex representation of a binary string
+ * for insertion into varbinary-type fields
+ * @return string
+ */
+ public function fetch() {
+ if ( $this->mData === null ) {
+ return 'null';
+ }
+
+ $ret = '0x';
+ $dataLength = strlen( $this->mData );
+ for ( $i = 0; $i < $dataLength; $i++ ) {
+ $ret .= bin2hex( pack( 'C', ord( $this->mData[$i] ) ) );
+ }
+
+ return $ret;
+ }
+}
--- /dev/null
+<?php
+class PostgresBlob extends Blob {
+
+}
--- /dev/null
+<?php
+/**
+ * This file contains database error classes.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Database
+ */
+
+/**
+ * Database error base class
+ * @ingroup Database
+ */
+class DBError extends Exception {
+ /** @var IDatabase|null */
+ public $db;
+
+ /**
+ * Construct a database error
+ * @param IDatabase $db Object which threw the error
+ * @param string $error A simple error message to be used for debugging
+ */
+ function __construct( IDatabase $db = null, $error ) {
+ $this->db = $db;
+ parent::__construct( $error );
+ }
+}
+
+/**
+ * Base class for the more common types of database errors. These are known to occur
+ * frequently, so we try to give friendly error messages for them.
+ *
+ * @ingroup Database
+ * @since 1.23
+ */
+class DBExpectedError extends DBError implements MessageSpecifier {
+ /** @var string[] Message parameters */
+ protected $params;
+
+ function __construct( IDatabase $db = null, $error, array $params = [] ) {
+ parent::__construct( $db, $error );
+ $this->params = $params;
+ }
+
+ public function getKey() {
+ return 'databaseerror-text';
+ }
+
+ public function getParams() {
+ return $this->params;
+ }
+}
+
+/**
+ * @ingroup Database
+ */
+class DBConnectionError extends DBExpectedError {
+ /**
+ * @param IDatabase $db Object throwing the error
+ * @param string $error Error text
+ */
+ function __construct( IDatabase $db = null, $error = 'unknown error' ) {
+ $msg = 'Cannot access the database';
+ if ( trim( $error ) != '' ) {
+ $msg .= ": $error";
+ }
+
+ parent::__construct( $db, $msg );
+ }
+}
+
+/**
+ * @ingroup Database
+ */
+class DBQueryError extends DBExpectedError {
+ /** @var string */
+ public $error;
+ /** @var integer */
+ public $errno;
+ /** @var string */
+ public $sql;
+ /** @var string */
+ public $fname;
+
+ /**
+ * @param IDatabase $db
+ * @param string $error
+ * @param int|string $errno
+ * @param string $sql
+ * @param string $fname
+ */
+ function __construct( IDatabase $db, $error, $errno, $sql, $fname ) {
+ if ( $db instanceof DatabaseBase && $db->wasConnectionError( $errno ) ) {
+ $message = "A connection error occured. \n" .
+ "Query: $sql\n" .
+ "Function: $fname\n" .
+ "Error: $errno $error\n";
+ } else {
+ $message = "A database error has occurred. Did you forget to run " .
+ "maintenance/update.php after upgrading? See: " .
+ "https://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" .
+ "Query: $sql\n" .
+ "Function: $fname\n" .
+ "Error: $errno $error\n";
+ }
+ parent::__construct( $db, $message );
+
+ $this->error = $error;
+ $this->errno = $errno;
+ $this->sql = $sql;
+ $this->fname = $fname;
+ }
+}
+
+/**
+ * @ingroup Database
+ */
+class DBReadOnlyError extends DBExpectedError {
+}
+
+/**
+ * @ingroup Database
+ */
+class DBTransactionError extends DBExpectedError {
+}
+
+/**
+ * @ingroup Database
+ */
+class DBTransactionSizeError extends DBTransactionError {
+ function getKey() {
+ return 'transaction-duration-limit-exceeded';
+ }
+}
+
+/**
+ * Exception class for replica DB wait timeouts
+ * @ingroup Database
+ */
+class DBReplicationWaitError extends DBExpectedError {
+}
+
+/**
+ * @ingroup Database
+ */
+class DBUnexpectedError extends DBError {
+}
+
+/**
+ * Exception class for attempted DB access
+ * @ingroup Database
+ */
+class DBAccessError extends DBUnexpectedError {
+ public function __construct() {
+ parent::__construct( "Mediawiki tried to access the database via wfGetDB(). " .
+ "This is not allowed, because database access has been disabled." );
+ }
+}
+
--- /dev/null
+<?php
+/**
+ * Base for all database-specific classes representing information about database fields
+ * @ingroup Database
+ */
+interface Field {
+ /**
+ * Field name
+ * @return string
+ */
+ function name();
+
+ /**
+ * Name of table this field belongs to
+ * @return string
+ */
+ function tableName();
+
+ /**
+ * Database type
+ * @return string
+ */
+ function type();
+
+ /**
+ * Whether this field can store NULL values
+ * @return bool
+ */
+ function isNullable();
+}
--- /dev/null
+<?php
+class MssqlField implements Field {
+ private $name, $tableName, $default, $max_length, $nullable, $type;
+
+ function __construct( $info ) {
+ $this->name = $info['COLUMN_NAME'];
+ $this->tableName = $info['TABLE_NAME'];
+ $this->default = $info['COLUMN_DEFAULT'];
+ $this->max_length = $info['CHARACTER_MAXIMUM_LENGTH'];
+ $this->nullable = !( strtolower( $info['IS_NULLABLE'] ) == 'no' );
+ $this->type = $info['DATA_TYPE'];
+ }
+
+ function name() {
+ return $this->name;
+ }
+
+ function tableName() {
+ return $this->tableName;
+ }
+
+ function defaultValue() {
+ return $this->default;
+ }
+
+ function maxLength() {
+ return $this->max_length;
+ }
+
+ function isNullable() {
+ return $this->nullable;
+ }
+
+ function type() {
+ return $this->type;
+ }
+}
+
--- /dev/null
+<?php
+class MySQLField implements Field {
+ private $name, $tablename, $default, $max_length, $nullable,
+ $is_pk, $is_unique, $is_multiple, $is_key, $type, $binary,
+ $is_numeric, $is_blob, $is_unsigned, $is_zerofill;
+
+ function __construct( $info ) {
+ $this->name = $info->name;
+ $this->tablename = $info->table;
+ $this->default = $info->def;
+ $this->max_length = $info->max_length;
+ $this->nullable = !$info->not_null;
+ $this->is_pk = $info->primary_key;
+ $this->is_unique = $info->unique_key;
+ $this->is_multiple = $info->multiple_key;
+ $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
+ $this->type = $info->type;
+ $this->binary = isset( $info->binary ) ? $info->binary : false;
+ $this->is_numeric = isset( $info->numeric ) ? $info->numeric : false;
+ $this->is_blob = isset( $info->blob ) ? $info->blob : false;
+ $this->is_unsigned = isset( $info->unsigned ) ? $info->unsigned : false;
+ $this->is_zerofill = isset( $info->zerofill ) ? $info->zerofill : false;
+ }
+
+ /**
+ * @return string
+ */
+ function name() {
+ return $this->name;
+ }
+
+ /**
+ * @return string
+ */
+ function tableName() {
+ return $this->tablename;
+ }
+
+ /**
+ * @return string
+ */
+ function type() {
+ return $this->type;
+ }
+
+ /**
+ * @return bool
+ */
+ function isNullable() {
+ return $this->nullable;
+ }
+
+ function defaultValue() {
+ return $this->default;
+ }
+
+ /**
+ * @return bool
+ */
+ function isKey() {
+ return $this->is_key;
+ }
+
+ /**
+ * @return bool
+ */
+ function isMultipleKey() {
+ return $this->is_multiple;
+ }
+
+ /**
+ * @return bool
+ */
+ function isBinary() {
+ return $this->binary;
+ }
+
+ /**
+ * @return bool
+ */
+ function isNumeric() {
+ return $this->is_numeric;
+ }
+
+ /**
+ * @return bool
+ */
+ function isBlob() {
+ return $this->is_blob;
+ }
+
+ /**
+ * @return bool
+ */
+ function isUnsigned() {
+ return $this->is_unsigned;
+ }
+
+ /**
+ * @return bool
+ */
+ function isZerofill() {
+ return $this->is_zerofill;
+ }
+}
+
--- /dev/null
+<?php
+class ORAField implements Field {
+ private $name, $tablename, $default, $max_length, $nullable,
+ $is_pk, $is_unique, $is_multiple, $is_key, $type;
+
+ function __construct( $info ) {
+ $this->name = $info['column_name'];
+ $this->tablename = $info['table_name'];
+ $this->default = $info['data_default'];
+ $this->max_length = $info['data_length'];
+ $this->nullable = $info['not_null'];
+ $this->is_pk = isset( $info['prim'] ) && $info['prim'] == 1 ? 1 : 0;
+ $this->is_unique = isset( $info['uniq'] ) && $info['uniq'] == 1 ? 1 : 0;
+ $this->is_multiple = isset( $info['nonuniq'] ) && $info['nonuniq'] == 1 ? 1 : 0;
+ $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
+ $this->type = $info['data_type'];
+ }
+
+ function name() {
+ return $this->name;
+ }
+
+ function tableName() {
+ return $this->tablename;
+ }
+
+ function defaultValue() {
+ return $this->default;
+ }
+
+ function maxLength() {
+ return $this->max_length;
+ }
+
+ function isNullable() {
+ return $this->nullable;
+ }
+
+ function isKey() {
+ return $this->is_key;
+ }
+
+ function isMultipleKey() {
+ return $this->is_multiple;
+ }
+
+ function type() {
+ return $this->type;
+ }
+}
--- /dev/null
+<?php
+class SQLiteField implements Field {
+ private $info, $tableName;
+
+ function __construct( $info, $tableName ) {
+ $this->info = $info;
+ $this->tableName = $tableName;
+ }
+
+ function name() {
+ return $this->info->name;
+ }
+
+ function tableName() {
+ return $this->tableName;
+ }
+
+ function defaultValue() {
+ if ( is_string( $this->info->dflt_value ) ) {
+ // Typically quoted
+ if ( preg_match( '/^\'(.*)\'$', $this->info->dflt_value ) ) {
+ return str_replace( "''", "'", $this->info->dflt_value );
+ }
+ }
+
+ return $this->info->dflt_value;
+ }
+
+ /**
+ * @return bool
+ */
+ function isNullable() {
+ return !$this->info->notnull;
+ }
+
+ function type() {
+ return $this->info->type;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Database load balancing interface
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Database
+ * @author Aaron Schulz
+ */
+
+/**
+ * Interface for database load balancing object that manages IDatabase handles
+ *
+ * @since 1.28
+ * @ingroup Database
+ */
+interface ILoadBalancer {
+ /**
+ * @param array $params Array with keys:
+ * - servers : Required. Array of server info structures.
+ * - loadMonitor : Name of a class used to fetch server lag and load.
+ * - readOnlyReason : Reason the master DB is read-only if so [optional]
+ * - waitTimeout : Maximum time to wait for replicas for consistency [optional]
+ * - srvCache : BagOStuff object for server cache [optional]
+ * - memCache : BagOStuff object for cluster memory cache [optional]
+ * - wanCache : WANObjectCache object [optional]
+ * - hostname : the name of the current server [optional]
+ * @throws InvalidArgumentException
+ */
+ public function __construct( array $params );
+
+ /**
+ * Get the index of the reader connection, which may be a replica DB
+ * This takes into account load ratios and lag times. It should
+ * always return a consistent index during a given invocation
+ *
+ * Side effect: opens connections to databases
+ * @param string|bool $group Query group, or false for the generic reader
+ * @param string|bool $wiki Wiki ID, or false for the current wiki
+ * @throws DBError
+ * @return bool|int|string
+ */
+ public function getReaderIndex( $group = false, $wiki = false );
+
+ /**
+ * Set the master wait position
+ * If a DB_REPLICA connection has been opened already, waits
+ * Otherwise sets a variable telling it to wait if such a connection is opened
+ * @param DBMasterPos $pos
+ */
+ public function waitFor( $pos );
+
+ /**
+ * Set the master wait position and wait for a "generic" replica DB to catch up to it
+ *
+ * This can be used a faster proxy for waitForAll()
+ *
+ * @param DBMasterPos $pos
+ * @param int $timeout Max seconds to wait; default is mWaitTimeout
+ * @return bool Success (able to connect and no timeouts reached)
+ */
+ public function waitForOne( $pos, $timeout = null );
+
+ /**
+ * Set the master wait position and wait for ALL replica DBs to catch up to it
+ * @param DBMasterPos $pos
+ * @param int $timeout Max seconds to wait; default is mWaitTimeout
+ * @return bool Success (able to connect and no timeouts reached)
+ */
+ public function waitForAll( $pos, $timeout = null );
+
+ /**
+ * Get any open connection to a given server index, local or foreign
+ * Returns false if there is no connection open
+ *
+ * @param int $i Server index
+ * @return IDatabase|bool False on failure
+ */
+ public function getAnyOpenConnection( $i );
+
+ /**
+ * Get a connection by index
+ * This is the main entry point for this class.
+ *
+ * @param int $i Server index
+ * @param array|string|bool $groups Query group(s), or false for the generic reader
+ * @param string|bool $wiki Wiki ID, or false for the current wiki
+ *
+ * @throws DBError
+ * @return IDatabase
+ */
+ public function getConnection( $i, $groups = [], $wiki = false );
+
+ /**
+ * Mark a foreign connection as being available for reuse under a different
+ * DB name or prefix. This mechanism is reference-counted, and must be called
+ * the same number of times as getConnection() to work.
+ *
+ * @param IDatabase $conn
+ * @throws InvalidArgumentException
+ */
+ public function reuseConnection( $conn );
+
+ /**
+ * Get a database connection handle reference
+ *
+ * The handle's methods wrap simply wrap those of a IDatabase handle
+ *
+ * @see LoadBalancer::getConnection() for parameter information
+ *
+ * @param int $db
+ * @param array|string|bool $groups Query group(s), or false for the generic reader
+ * @param string|bool $wiki Wiki ID, or false for the current wiki
+ * @return DBConnRef
+ */
+ public function getConnectionRef( $db, $groups = [], $wiki = false );
+
+ /**
+ * Get a database connection handle reference without connecting yet
+ *
+ * The handle's methods wrap simply wrap those of a IDatabase handle
+ *
+ * @see LoadBalancer::getConnection() for parameter information
+ *
+ * @param int $db
+ * @param array|string|bool $groups Query group(s), or false for the generic reader
+ * @param string|bool $wiki Wiki ID, or false for the current wiki
+ * @return DBConnRef
+ */
+ public function getLazyConnectionRef( $db, $groups = [], $wiki = false );
+
+ /**
+ * Open a connection to the server given by the specified index
+ * Index must be an actual index into the array.
+ * If the server is already open, returns it.
+ *
+ * On error, returns false, and the connection which caused the
+ * error will be available via $this->mErrorConnection.
+ *
+ * @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError.
+ *
+ * @param int $i Server index
+ * @param string|bool $wiki Wiki ID, or false for the current wiki
+ * @return IDatabase|bool Returns false on errors
+ */
+ public function openConnection( $i, $wiki = false );
+
+ /**
+ * @return int
+ */
+ public function getWriterIndex();
+
+ /**
+ * Returns true if the specified index is a valid server index
+ *
+ * @param string $i
+ * @return bool
+ */
+ public function haveIndex( $i );
+
+ /**
+ * Returns true if the specified index is valid and has non-zero load
+ *
+ * @param string $i
+ * @return bool
+ */
+ public function isNonZeroLoad( $i );
+
+ /**
+ * Get the number of defined servers (not the number of open connections)
+ *
+ * @return int
+ */
+ public function getServerCount();
+
+ /**
+ * Get the host name or IP address of the server with the specified index
+ * Prefer a readable name if available.
+ * @param string $i
+ * @return string
+ */
+ public function getServerName( $i );
+
+ /**
+ * Return the server info structure for a given index, or false if the index is invalid.
+ * @param int $i
+ * @return array|bool
+ */
+ public function getServerInfo( $i );
+
+ /**
+ * Sets the server info structure for the given index. Entry at index $i
+ * is created if it doesn't exist
+ * @param int $i
+ * @param array $serverInfo
+ */
+ public function setServerInfo( $i, array $serverInfo );
+
+ /**
+ * Get the current master position for chronology control purposes
+ * @return DBMasterPos|bool Returns false if not applicable
+ */
+ public function getMasterPos();
+
+ /**
+ * Disable this load balancer. All connections are closed, and any attempt to
+ * open a new connection will result in a DBAccessError.
+ */
+ public function disable();
+
+ /**
+ * Close all open connections
+ */
+ public function closeAll();
+
+ /**
+ * Close a connection
+ *
+ * Using this function makes sure the LoadBalancer knows the connection is closed.
+ * If you use $conn->close() directly, the load balancer won't update its state.
+ *
+ * @param IDatabase $conn
+ */
+ public function closeConnection( IDatabase $conn );
+
+ /**
+ * Commit transactions on all open connections
+ * @param string $fname Caller name
+ * @throws DBExpectedError
+ */
+ public function commitAll( $fname = __METHOD__ );
+
+ /**
+ * Perform all pre-commit callbacks that remain part of the atomic transactions
+ * and disable any post-commit callbacks until runMasterPostTrxCallbacks()
+ *
+ * Use this only for mutli-database commits
+ */
+ public function finalizeMasterChanges();
+
+ /**
+ * Perform all pre-commit checks for things like replication safety
+ *
+ * Use this only for mutli-database commits
+ *
+ * @param array $options Includes:
+ * - maxWriteDuration : max write query duration time in seconds
+ * @throws DBTransactionError
+ */
+ public function approveMasterChanges( array $options );
+
+ /**
+ * Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
+ *
+ * The DBO_TRX setting will be reverted to the default in each of these methods:
+ * - commitMasterChanges()
+ * - rollbackMasterChanges()
+ * - commitAll()
+ * This allows for custom transaction rounds from any outer transaction scope.
+ *
+ * @param string $fname
+ * @throws DBExpectedError
+ */
+ public function beginMasterChanges( $fname = __METHOD__ );
+
+ /**
+ * Issue COMMIT on all master connections where writes where done
+ * @param string $fname Caller name
+ * @throws DBExpectedError
+ */
+ public function commitMasterChanges( $fname = __METHOD__ );
+
+ /**
+ * Issue all pending post-COMMIT/ROLLBACK callbacks
+ *
+ * Use this only for mutli-database commits
+ *
+ * @param integer $type IDatabase::TRIGGER_* constant
+ * @return Exception|null The first exception or null if there were none
+ */
+ public function runMasterPostTrxCallbacks( $type );
+
+ /**
+ * Issue ROLLBACK only on master, only if queries were done on connection
+ * @param string $fname Caller name
+ * @throws DBExpectedError
+ */
+ public function rollbackMasterChanges( $fname = __METHOD__ );
+
+ /**
+ * Suppress all pending post-COMMIT/ROLLBACK callbacks
+ *
+ * Use this only for mutli-database commits
+ *
+ * @return Exception|null The first exception or null if there were none
+ */
+ public function suppressTransactionEndCallbacks();
+
+ /**
+ * Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot
+ *
+ * @param string $fname Caller name
+ */
+ public function flushReplicaSnapshots( $fname = __METHOD__ );
+
+ /**
+ * @return bool Whether a master connection is already open
+ */
+ public function hasMasterConnection();
+
+ /**
+ * Determine if there are pending changes in a transaction by this thread
+ * @return bool
+ */
+ public function hasMasterChanges();
+
+ /**
+ * Get the timestamp of the latest write query done by this thread
+ * @return float|bool UNIX timestamp or false
+ */
+ public function lastMasterChangeTimestamp();
+
+ /**
+ * Check if this load balancer object had any recent or still
+ * pending writes issued against it by this PHP thread
+ *
+ * @param float $age How many seconds ago is "recent" [defaults to mWaitTimeout]
+ * @return bool
+ */
+ public function hasOrMadeRecentMasterChanges( $age = null );
+
+ /**
+ * Get the list of callers that have pending master changes
+ *
+ * @return string[] List of method names
+ */
+ public function pendingMasterChangeCallers();
+
+ /**
+ * @note This method will trigger a DB connection if not yet done
+ * @param string|bool $wiki Wiki ID, or false for the current wiki
+ * @return bool Whether the generic connection for reads is highly "lagged"
+ */
+ public function getLaggedReplicaMode( $wiki = false );
+
+ /**
+ * @note This method will never cause a new DB connection
+ * @return bool Whether any generic connection used for reads was highly "lagged"
+ */
+ public function laggedReplicaUsed();
+
+ /**
+ * @note This method may trigger a DB connection if not yet done
+ * @param string|bool $wiki Wiki ID, or false for the current wiki
+ * @param IDatabase|null DB master connection; used to avoid loops [optional]
+ * @return string|bool Reason the master is read-only or false if it is not
+ */
+ public function getReadOnlyReason( $wiki = false, IDatabase $conn = null );
+
+ /**
+ * Disables/enables lag checks
+ * @param null|bool $mode
+ * @return bool
+ */
+ public function allowLagged( $mode = null );
+
+ /**
+ * @return bool
+ */
+ public function pingAll();
+
+ /**
+ * Call a function with each open connection object
+ * @param callable $callback
+ * @param array $params
+ */
+ public function forEachOpenConnection( $callback, array $params = [] );
+
+ /**
+ * Call a function with each open connection object to a master
+ * @param callable $callback
+ * @param array $params
+ */
+ public function forEachOpenMasterConnection( $callback, array $params = [] );
+
+ /**
+ * Call a function with each open replica DB connection object
+ * @param callable $callback
+ * @param array $params
+ */
+ public function forEachOpenReplicaConnection( $callback, array $params = [] );
+
+ /**
+ * Get the hostname and lag time of the most-lagged replica DB
+ *
+ * This is useful for maintenance scripts that need to throttle their updates.
+ * May attempt to open connections to replica DBs on the default DB. If there is
+ * no lag, the maximum lag will be reported as -1.
+ *
+ * @param bool|string $wiki Wiki ID, or false for the default database
+ * @return array ( host, max lag, index of max lagged host )
+ */
+ public function getMaxLag( $wiki = false );
+
+ /**
+ * Get an estimate of replication lag (in seconds) for each server
+ *
+ * Results are cached for a short time in memcached/process cache
+ *
+ * Values may be "false" if replication is too broken to estimate
+ *
+ * @param string|bool $wiki
+ * @return int[] Map of (server index => float|int|bool)
+ */
+ public function getLagTimes( $wiki = false );
+
+ /**
+ * Get the lag in seconds for a given connection, or zero if this load
+ * balancer does not have replication enabled.
+ *
+ * This should be used in preference to Database::getLag() in cases where
+ * replication may not be in use, since there is no way to determine if
+ * replication is in use at the connection level without running
+ * potentially restricted queries such as SHOW SLAVE STATUS. Using this
+ * function instead of Database::getLag() avoids a fatal error in this
+ * case on many installations.
+ *
+ * @param IDatabase $conn
+ * @return int|bool Returns false on error
+ */
+ public function safeGetLag( IDatabase $conn );
+
+ /**
+ * Wait for a replica DB to reach a specified master position
+ *
+ * This will connect to the master to get an accurate position if $pos is not given
+ *
+ * @param IDatabase $conn Replica DB
+ * @param DBMasterPos|bool $pos Master position; default: current position
+ * @param integer|null $timeout Timeout in seconds [optional]
+ * @return bool Success
+ */
+ public function safeWaitForMasterPos( IDatabase $conn, $pos = false, $timeout = null );
+
+ /**
+ * Clear the cache for slag lag delay times
+ *
+ * This is only used for testing
+ */
+ public function clearLagTimeCache();
+
+ /**
+ * Set a callback via IDatabase::setTransactionListener() on
+ * all current and future master connections of this load balancer
+ *
+ * @param string $name Callback name
+ * @param callable|null $callback
+ */
+ public function setTransactionListener( $name, callable $callback = null );
+}
--- /dev/null
+<?php
+/**
+ * Database load balancing manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Database
+ */
+use Psr\Log\LoggerInterface;
+
+/**
+ * Database load balancing, tracking, and transaction management object
+ *
+ * @ingroup Database
+ */
+class LoadBalancer implements ILoadBalancer {
+ /** @var array[] Map of (server index => server config array) */
+ private $mServers;
+ /** @var array[] Map of (local/foreignUsed/foreignFree => server index => IDatabase array) */
+ private $mConns;
+ /** @var array Map of (server index => weight) */
+ private $mLoads;
+ /** @var array[] Map of (group => server index => weight) */
+ private $mGroupLoads;
+ /** @var bool Whether to disregard replica DB lag as a factor in replica DB selection */
+ private $mAllowLagged;
+ /** @var integer Seconds to spend waiting on replica DB lag to resolve */
+ private $mWaitTimeout;
+ /** @var string The LoadMonitor subclass name */
+ private $mLoadMonitorClass;
+
+ /** @var LoadMonitor */
+ private $mLoadMonitor;
+ /** @var BagOStuff */
+ private $srvCache;
+ /** @var BagOStuff */
+ private $memCache;
+ /** @var WANObjectCache */
+ private $wanCache;
+ /** @var TransactionProfiler */
+ protected $trxProfiler;
+ /** @var LoggerInterface */
+ protected $replLogger;
+ /** @var LoggerInterface */
+ protected $connLogger;
+ /** @var LoggerInterface */
+ protected $queryLogger;
+ /** @var LoggerInterface */
+ protected $perfLogger;
+
+ /** @var bool|IDatabase Database connection that caused a problem */
+ private $mErrorConnection;
+ /** @var integer The generic (not query grouped) replica DB index (of $mServers) */
+ private $mReadIndex;
+ /** @var bool|DBMasterPos False if not set */
+ private $mWaitForPos;
+ /** @var bool Whether the generic reader fell back to a lagged replica DB */
+ private $laggedReplicaMode = false;
+ /** @var bool Whether the generic reader fell back to a lagged replica DB */
+ private $allReplicasDownMode = false;
+ /** @var string The last DB selection or connection error */
+ private $mLastError = 'Unknown error';
+ /** @var string|bool Reason the LB is read-only or false if not */
+ private $readOnlyReason = false;
+ /** @var integer Total connections opened */
+ private $connsOpened = 0;
+ /** @var string|bool String if a requested DBO_TRX transaction round is active */
+ private $trxRoundId = false;
+ /** @var array[] Map of (name => callable) */
+ private $trxRecurringCallbacks = [];
+ /** @var string Local Domain ID and default for selectDB() calls */
+ private $localDomain;
+ /** @var string Current server name */
+ private $host;
+
+ /** @var callable Exception logger */
+ private $errorLogger;
+
+ /** @var boolean */
+ private $disabled = false;
+
+ /** @var integer Warn when this many connection are held */
+ const CONN_HELD_WARN_THRESHOLD = 10;
+ /** @var integer Default 'max lag' when unspecified */
+ const MAX_LAG_DEFAULT = 10;
+ /** @var integer Max time to wait for a replica DB to catch up (e.g. ChronologyProtector) */
+ const POS_WAIT_TIMEOUT = 10;
+ /** @var integer Seconds to cache master server read-only status */
+ const TTL_CACHE_READONLY = 5;
+
+ public function __construct( array $params ) {
+ if ( !isset( $params['servers'] ) ) {
+ throw new InvalidArgumentException( __CLASS__ . ': missing servers parameter' );
+ }
+ $this->mServers = $params['servers'];
+ $this->mWaitTimeout = isset( $params['waitTimeout'] )
+ ? $params['waitTimeout']
+ : self::POS_WAIT_TIMEOUT;
+ $this->localDomain = isset( $params['localDomain'] ) ? $params['localDomain'] : '';
+
+ $this->mReadIndex = -1;
+ $this->mConns = [
+ 'local' => [],
+ 'foreignUsed' => [],
+ 'foreignFree' => [] ];
+ $this->mLoads = [];
+ $this->mWaitForPos = false;
+ $this->mErrorConnection = false;
+ $this->mAllowLagged = false;
+
+ if ( isset( $params['readOnlyReason'] ) && is_string( $params['readOnlyReason'] ) ) {
+ $this->readOnlyReason = $params['readOnlyReason'];
+ }
+
+ if ( isset( $params['loadMonitor'] ) ) {
+ $this->mLoadMonitorClass = $params['loadMonitor'];
+ } else {
+ $master = reset( $params['servers'] );
+ if ( isset( $master['type'] ) && $master['type'] === 'mysql' ) {
+ $this->mLoadMonitorClass = 'LoadMonitorMySQL';
+ } else {
+ $this->mLoadMonitorClass = 'LoadMonitorNull';
+ }
+ }
+
+ foreach ( $params['servers'] as $i => $server ) {
+ $this->mLoads[$i] = $server['load'];
+ if ( isset( $server['groupLoads'] ) ) {
+ foreach ( $server['groupLoads'] as $group => $ratio ) {
+ if ( !isset( $this->mGroupLoads[$group] ) ) {
+ $this->mGroupLoads[$group] = [];
+ }
+ $this->mGroupLoads[$group][$i] = $ratio;
+ }
+ }
+ }
+
+ if ( isset( $params['srvCache'] ) ) {
+ $this->srvCache = $params['srvCache'];
+ } else {
+ $this->srvCache = new EmptyBagOStuff();
+ }
+ if ( isset( $params['memCache'] ) ) {
+ $this->memCache = $params['memCache'];
+ } else {
+ $this->memCache = new EmptyBagOStuff();
+ }
+ if ( isset( $params['wanCache'] ) ) {
+ $this->wanCache = $params['wanCache'];
+ } else {
+ $this->wanCache = WANObjectCache::newEmpty();
+ }
+ if ( isset( $params['trxProfiler'] ) ) {
+ $this->trxProfiler = $params['trxProfiler'];
+ } else {
+ $this->trxProfiler = new TransactionProfiler();
+ }
+
+ $this->errorLogger = isset( $params['errorLogger'] )
+ ? $params['errorLogger']
+ : function ( Exception $e ) {
+ trigger_error( E_WARNING, $e->getMessage() );
+ };
+
+ foreach ( [ 'replLogger', 'connLogger', 'queryLogger', 'perfLogger' ] as $key ) {
+ $this->$key = isset( $params[$key] ) ? $params[$key] : new \Psr\Log\NullLogger();
+ }
+
+ $this->host = isset( $params['hostname'] )
+ ? $params['hostname']
+ : ( gethostname() ?: 'unknown' );
+ }
+
+ /**
+ * Get a LoadMonitor instance
+ *
+ * @return LoadMonitor
+ */
+ private function getLoadMonitor() {
+ if ( !isset( $this->mLoadMonitor ) ) {
+ $class = $this->mLoadMonitorClass;
+ $this->mLoadMonitor = new $class( $this, $this->srvCache, $this->memCache );
+ $this->mLoadMonitor->setLogger( $this->replLogger );
+ }
+
+ return $this->mLoadMonitor;
+ }
+
+ /**
+ * @param array $loads
+ * @param bool|string $domain Domain to get non-lagged for
+ * @param int $maxLag Restrict the maximum allowed lag to this many seconds
+ * @return bool|int|string
+ */
+ private function getRandomNonLagged( array $loads, $domain = false, $maxLag = INF ) {
+ $lags = $this->getLagTimes( $domain );
+
+ # Unset excessively lagged servers
+ foreach ( $lags as $i => $lag ) {
+ if ( $i != 0 ) {
+ # How much lag this server nominally is allowed to have
+ $maxServerLag = isset( $this->mServers[$i]['max lag'] )
+ ? $this->mServers[$i]['max lag']
+ : self::MAX_LAG_DEFAULT; // default
+ # Constrain that futher by $maxLag argument
+ $maxServerLag = min( $maxServerLag, $maxLag );
+
+ $host = $this->getServerName( $i );
+ if ( $lag === false && !is_infinite( $maxServerLag ) ) {
+ $this->replLogger->error( "Server $host (#$i) is not replicating?" );
+ unset( $loads[$i] );
+ } elseif ( $lag > $maxServerLag ) {
+ $this->replLogger->warning( "Server $host (#$i) has >= $lag seconds of lag" );
+ unset( $loads[$i] );
+ }
+ }
+ }
+
+ # Find out if all the replica DBs with non-zero load are lagged
+ $sum = 0;
+ foreach ( $loads as $load ) {
+ $sum += $load;
+ }
+ if ( $sum == 0 ) {
+ # No appropriate DB servers except maybe the master and some replica DBs with zero load
+ # Do NOT use the master
+ # Instead, this function will return false, triggering read-only mode,
+ # and a lagged replica DB will be used instead.
+ return false;
+ }
+
+ if ( count( $loads ) == 0 ) {
+ return false;
+ }
+
+ # Return a random representative of the remainder
+ return ArrayUtils::pickRandom( $loads );
+ }
+
+ public function getReaderIndex( $group = false, $domain = false ) {
+ if ( count( $this->mServers ) == 1 ) {
+ # Skip the load balancing if there's only one server
+ return $this->getWriterIndex();
+ } elseif ( $group === false && $this->mReadIndex >= 0 ) {
+ # Shortcut if generic reader exists already
+ return $this->mReadIndex;
+ }
+
+ # Find the relevant load array
+ if ( $group !== false ) {
+ if ( isset( $this->mGroupLoads[$group] ) ) {
+ $nonErrorLoads = $this->mGroupLoads[$group];
+ } else {
+ # No loads for this group, return false and the caller can use some other group
+ $this->connLogger->info( __METHOD__ . ": no loads for group $group" );
+
+ return false;
+ }
+ } else {
+ $nonErrorLoads = $this->mLoads;
+ }
+
+ if ( !count( $nonErrorLoads ) ) {
+ throw new InvalidArgumentException( "Empty server array given to LoadBalancer" );
+ }
+
+ # Scale the configured load ratios according to the dynamic load if supported
+ $this->getLoadMonitor()->scaleLoads( $nonErrorLoads, $group, $domain );
+
+ $laggedReplicaMode = false;
+
+ # No server found yet
+ $i = false;
+ # First try quickly looking through the available servers for a server that
+ # meets our criteria
+ $currentLoads = $nonErrorLoads;
+ while ( count( $currentLoads ) ) {
+ if ( $this->mAllowLagged || $laggedReplicaMode ) {
+ $i = ArrayUtils::pickRandom( $currentLoads );
+ } else {
+ $i = false;
+ if ( $this->mWaitForPos && $this->mWaitForPos->asOfTime() ) {
+ # ChronologyProtecter causes mWaitForPos to be set via sessions.
+ # This triggers doWait() after connect, so it's especially good to
+ # avoid lagged servers so as to avoid just blocking in that method.
+ $ago = microtime( true ) - $this->mWaitForPos->asOfTime();
+ # Aim for <= 1 second of waiting (being too picky can backfire)
+ $i = $this->getRandomNonLagged( $currentLoads, $domain, $ago + 1 );
+ }
+ if ( $i === false ) {
+ # Any server with less lag than it's 'max lag' param is preferable
+ $i = $this->getRandomNonLagged( $currentLoads, $domain );
+ }
+ if ( $i === false && count( $currentLoads ) != 0 ) {
+ # All replica DBs lagged. Switch to read-only mode
+ $this->replLogger->error( "All replica DBs lagged. Switch to read-only mode" );
+ $i = ArrayUtils::pickRandom( $currentLoads );
+ $laggedReplicaMode = true;
+ }
+ }
+
+ if ( $i === false ) {
+ # pickRandom() returned false
+ # This is permanent and means the configuration or the load monitor
+ # wants us to return false.
+ $this->connLogger->debug( __METHOD__ . ": pickRandom() returned false" );
+
+ return false;
+ }
+
+ $serverName = $this->getServerName( $i );
+ $this->connLogger->debug( __METHOD__ . ": Using reader #$i: $serverName..." );
+
+ $conn = $this->openConnection( $i, $domain );
+ if ( !$conn ) {
+ $this->connLogger->warning( __METHOD__ . ": Failed connecting to $i/$domain" );
+ unset( $nonErrorLoads[$i] );
+ unset( $currentLoads[$i] );
+ $i = false;
+ continue;
+ }
+
+ // Decrement reference counter, we are finished with this connection.
+ // It will be incremented for the caller later.
+ if ( $domain !== false ) {
+ $this->reuseConnection( $conn );
+ }
+
+ # Return this server
+ break;
+ }
+
+ # If all servers were down, quit now
+ if ( !count( $nonErrorLoads ) ) {
+ $this->connLogger->error( "All servers down" );
+ }
+
+ if ( $i !== false ) {
+ # Replica DB connection successful.
+ # Wait for the session master pos for a short time.
+ if ( $this->mWaitForPos && $i > 0 ) {
+ $this->doWait( $i );
+ }
+ if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $group === false ) {
+ $this->mReadIndex = $i;
+ # Record if the generic reader index is in "lagged replica DB" mode
+ if ( $laggedReplicaMode ) {
+ $this->laggedReplicaMode = true;
+ }
+ }
+ $serverName = $this->getServerName( $i );
+ $this->connLogger->debug(
+ __METHOD__ . ": using server $serverName for group '$group'" );
+ }
+
+ return $i;
+ }
+
+ public function waitFor( $pos ) {
+ $this->mWaitForPos = $pos;
+ $i = $this->mReadIndex;
+
+ if ( $i > 0 ) {
+ if ( !$this->doWait( $i ) ) {
+ $this->laggedReplicaMode = true;
+ }
+ }
+ }
+
+ /**
+ * Set the master wait position and wait for a "generic" replica DB to catch up to it
+ *
+ * This can be used a faster proxy for waitForAll()
+ *
+ * @param DBMasterPos $pos
+ * @param int $timeout Max seconds to wait; default is mWaitTimeout
+ * @return bool Success (able to connect and no timeouts reached)
+ * @since 1.26
+ */
+ public function waitForOne( $pos, $timeout = null ) {
+ $this->mWaitForPos = $pos;
+
+ $i = $this->mReadIndex;
+ if ( $i <= 0 ) {
+ // Pick a generic replica DB if there isn't one yet
+ $readLoads = $this->mLoads;
+ unset( $readLoads[$this->getWriterIndex()] ); // replica DBs only
+ $readLoads = array_filter( $readLoads ); // with non-zero load
+ $i = ArrayUtils::pickRandom( $readLoads );
+ }
+
+ if ( $i > 0 ) {
+ $ok = $this->doWait( $i, true, $timeout );
+ } else {
+ $ok = true; // no applicable loads
+ }
+
+ return $ok;
+ }
+
+ public function waitForAll( $pos, $timeout = null ) {
+ $this->mWaitForPos = $pos;
+ $serverCount = count( $this->mServers );
+
+ $ok = true;
+ for ( $i = 1; $i < $serverCount; $i++ ) {
+ if ( $this->mLoads[$i] > 0 ) {
+ $ok = $this->doWait( $i, true, $timeout ) && $ok;
+ }
+ }
+
+ return $ok;
+ }
+
+ public function getAnyOpenConnection( $i ) {
+ foreach ( $this->mConns as $connsByServer ) {
+ if ( !empty( $connsByServer[$i] ) ) {
+ return reset( $connsByServer[$i] );
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Wait for a given replica DB to catch up to the master pos stored in $this
+ * @param int $index Server index
+ * @param bool $open Check the server even if a new connection has to be made
+ * @param int $timeout Max seconds to wait; default is mWaitTimeout
+ * @return bool
+ */
+ protected function doWait( $index, $open = false, $timeout = null ) {
+ $close = false; // close the connection afterwards
+
+ // Check if we already know that the DB has reached this point
+ $server = $this->getServerName( $index );
+ $key = $this->srvCache->makeGlobalKey( __CLASS__, 'last-known-pos', $server );
+ /** @var DBMasterPos $knownReachedPos */
+ $knownReachedPos = $this->srvCache->get( $key );
+ if ( $knownReachedPos && $knownReachedPos->hasReached( $this->mWaitForPos ) ) {
+ $this->replLogger->debug( __METHOD__ .
+ ": replica DB $server known to be caught up (pos >= $knownReachedPos)." );
+ return true;
+ }
+
+ // Find a connection to wait on, creating one if needed and allowed
+ $conn = $this->getAnyOpenConnection( $index );
+ if ( !$conn ) {
+ if ( !$open ) {
+ $this->replLogger->debug( __METHOD__ . ": no connection open for $server" );
+
+ return false;
+ } else {
+ $conn = $this->openConnection( $index, '' );
+ if ( !$conn ) {
+ $this->replLogger->warning( __METHOD__ . ": failed to connect to $server" );
+
+ return false;
+ }
+ // Avoid connection spam in waitForAll() when connections
+ // are made just for the sake of doing this lag check.
+ $close = true;
+ }
+ }
+
+ $this->replLogger->info( __METHOD__ . ": Waiting for replica DB $server to catch up..." );
+ $timeout = $timeout ?: $this->mWaitTimeout;
+ $result = $conn->masterPosWait( $this->mWaitForPos, $timeout );
+
+ if ( $result == -1 || is_null( $result ) ) {
+ // Timed out waiting for replica DB, use master instead
+ $msg = __METHOD__ . ": Timed out waiting on $server pos {$this->mWaitForPos}";
+ $this->replLogger->warning( "$msg" );
+ $ok = false;
+ } else {
+ $this->replLogger->info( __METHOD__ . ": Done" );
+ $ok = true;
+ // Remember that the DB reached this point
+ $this->srvCache->set( $key, $this->mWaitForPos, BagOStuff::TTL_DAY );
+ }
+
+ if ( $close ) {
+ $this->closeConnection( $conn );
+ }
+
+ return $ok;
+ }
+
+ public function getConnection( $i, $groups = [], $domain = false ) {
+ if ( $i === null || $i === false ) {
+ throw new InvalidArgumentException( 'Attempt to call ' . __METHOD__ .
+ ' with invalid server index' );
+ }
+
+ if ( $domain === $this->localDomain ) {
+ $domain = false;
+ }
+
+ $groups = ( $groups === false || $groups === [] )
+ ? [ false ] // check one "group": the generic pool
+ : (array)$groups;
+
+ $masterOnly = ( $i == DB_MASTER || $i == $this->getWriterIndex() );
+ $oldConnsOpened = $this->connsOpened; // connections open now
+
+ if ( $i == DB_MASTER ) {
+ $i = $this->getWriterIndex();
+ } else {
+ # Try to find an available server in any the query groups (in order)
+ foreach ( $groups as $group ) {
+ $groupIndex = $this->getReaderIndex( $group, $domain );
+ if ( $groupIndex !== false ) {
+ $i = $groupIndex;
+ break;
+ }
+ }
+ }
+
+ # Operation-based index
+ if ( $i == DB_REPLICA ) {
+ $this->mLastError = 'Unknown error'; // reset error string
+ # Try the general server pool if $groups are unavailable.
+ $i = in_array( false, $groups, true )
+ ? false // don't bother with this if that is what was tried above
+ : $this->getReaderIndex( false, $domain );
+ # Couldn't find a working server in getReaderIndex()?
+ if ( $i === false ) {
+ $this->mLastError = 'No working replica DB server: ' . $this->mLastError;
+
+ return $this->reportConnectionError();
+ }
+ }
+
+ # Now we have an explicit index into the servers array
+ $conn = $this->openConnection( $i, $domain );
+ if ( !$conn ) {
+ return $this->reportConnectionError();
+ }
+
+ # Profile any new connections that happen
+ if ( $this->connsOpened > $oldConnsOpened ) {
+ $host = $conn->getServer();
+ $dbname = $conn->getDBname();
+ $this->trxProfiler->recordConnection( $host, $dbname, $masterOnly );
+ }
+
+ if ( $masterOnly ) {
+ # Make master-requested DB handles inherit any read-only mode setting
+ $conn->setLBInfo( 'readOnlyReason', $this->getReadOnlyReason( $domain, $conn ) );
+ }
+
+ return $conn;
+ }
+
+ public function reuseConnection( $conn ) {
+ $serverIndex = $conn->getLBInfo( 'serverIndex' );
+ $refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
+ if ( $serverIndex === null || $refCount === null ) {
+ /**
+ * This can happen in code like:
+ * foreach ( $dbs as $db ) {
+ * $conn = $lb->getConnection( DB_REPLICA, [], $db );
+ * ...
+ * $lb->reuseConnection( $conn );
+ * }
+ * When a connection to the local DB is opened in this way, reuseConnection()
+ * should be ignored
+ */
+ return;
+ }
+
+ $dbName = $conn->getDBname();
+ $prefix = $conn->tablePrefix();
+ if ( strval( $prefix ) !== '' ) {
+ $domain = "$dbName-$prefix";
+ } else {
+ $domain = $dbName;
+ }
+ if ( $this->mConns['foreignUsed'][$serverIndex][$domain] !== $conn ) {
+ throw new InvalidArgumentException( __METHOD__ . ": connection not found, has " .
+ "the connection been freed already?" );
+ }
+ $conn->setLBInfo( 'foreignPoolRefCount', --$refCount );
+ if ( $refCount <= 0 ) {
+ $this->mConns['foreignFree'][$serverIndex][$domain] = $conn;
+ unset( $this->mConns['foreignUsed'][$serverIndex][$domain] );
+ $this->connLogger->debug( __METHOD__ . ": freed connection $serverIndex/$domain" );
+ } else {
+ $this->connLogger->debug( __METHOD__ .
+ ": reference count for $serverIndex/$domain reduced to $refCount" );
+ }
+ }
+
+ /**
+ * Get a database connection handle reference
+ *
+ * The handle's methods wrap simply wrap those of a IDatabase handle
+ *
+ * @see LoadBalancer::getConnection() for parameter information
+ *
+ * @param int $db
+ * @param array|string|bool $groups Query group(s), or false for the generic reader
+ * @param string|bool $domain Domain ID, or false for the current domain
+ * @return DBConnRef
+ * @since 1.22
+ */
+ public function getConnectionRef( $db, $groups = [], $domain = false ) {
+ return new DBConnRef( $this, $this->getConnection( $db, $groups, $domain ) );
+ }
+
+ /**
+ * Get a database connection handle reference without connecting yet
+ *
+ * The handle's methods wrap simply wrap those of a IDatabase handle
+ *
+ * @see LoadBalancer::getConnection() for parameter information
+ *
+ * @param int $db
+ * @param array|string|bool $groups Query group(s), or false for the generic reader
+ * @param string|bool $domain Domain ID, or false for the current domain
+ * @return DBConnRef
+ * @since 1.22
+ */
+ public function getLazyConnectionRef( $db, $groups = [], $domain = false ) {
+ $domain = ( $domain !== false ) ? $domain : $this->localDomain;
+
+ return new DBConnRef( $this, [ $db, $groups, $domain ] );
+ }
+
+ public function openConnection( $i, $domain = false ) {
+ if ( $domain !== false ) {
+ $conn = $this->openForeignConnection( $i, $domain );
+ } elseif ( isset( $this->mConns['local'][$i][0] ) ) {
+ $conn = $this->mConns['local'][$i][0];
+ } else {
+ $server = $this->mServers[$i];
+ $server['serverIndex'] = $i;
+ $conn = $this->reallyOpenConnection( $server, false );
+ $serverName = $this->getServerName( $i );
+ if ( $conn->isOpen() ) {
+ $this->connLogger->debug( "Connected to database $i at '$serverName'." );
+ $this->mConns['local'][$i][0] = $conn;
+ } else {
+ $this->connLogger->warning( "Failed to connect to database $i at '$serverName'." );
+ $this->mErrorConnection = $conn;
+ $conn = false;
+ }
+ }
+
+ if ( $conn && !$conn->isOpen() ) {
+ // Connection was made but later unrecoverably lost for some reason.
+ // Do not return a handle that will just throw exceptions on use,
+ // but let the calling code (e.g. getReaderIndex) try another server.
+ // See DatabaseMyslBase::ping() for how this can happen.
+ $this->mErrorConnection = $conn;
+ $conn = false;
+ }
+
+ return $conn;
+ }
+
+ /**
+ * Open a connection to a foreign DB, or return one if it is already open.
+ *
+ * Increments a reference count on the returned connection which locks the
+ * connection to the requested domain. This reference count can be
+ * decremented by calling reuseConnection().
+ *
+ * If a connection is open to the appropriate server already, but with the wrong
+ * database, it will be switched to the right database and returned, as long as
+ * it has been freed first with reuseConnection().
+ *
+ * On error, returns false, and the connection which caused the
+ * error will be available via $this->mErrorConnection.
+ *
+ * @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError.
+ *
+ * @param int $i Server index
+ * @param string $domain Domain ID to open
+ * @return IDatabase
+ */
+ private function openForeignConnection( $i, $domain ) {
+ list( $dbName, $prefix ) = explode( '-', $domain, 2 ) + [ '', '' ];
+
+ if ( isset( $this->mConns['foreignUsed'][$i][$domain] ) ) {
+ // Reuse an already-used connection
+ $conn = $this->mConns['foreignUsed'][$i][$domain];
+ $this->connLogger->debug( __METHOD__ . ": reusing connection $i/$domain" );
+ } elseif ( isset( $this->mConns['foreignFree'][$i][$domain] ) ) {
+ // Reuse a free connection for the same domain
+ $conn = $this->mConns['foreignFree'][$i][$domain];
+ unset( $this->mConns['foreignFree'][$i][$domain] );
+ $this->mConns['foreignUsed'][$i][$domain] = $conn;
+ $this->connLogger->debug( __METHOD__ . ": reusing free connection $i/$domain" );
+ } elseif ( !empty( $this->mConns['foreignFree'][$i] ) ) {
+ // Reuse a connection from another domain
+ $conn = reset( $this->mConns['foreignFree'][$i] );
+ $oldDomain = key( $this->mConns['foreignFree'][$i] );
+
+ // The empty string as a DB name means "don't care".
+ // DatabaseMysqlBase::open() already handle this on connection.
+ if ( $dbName !== '' && !$conn->selectDB( $dbName ) ) {
+ $this->mLastError = "Error selecting database $dbName on server " .
+ $conn->getServer() . " from client host {$this->host}";
+ $this->mErrorConnection = $conn;
+ $conn = false;
+ } else {
+ $conn->tablePrefix( $prefix );
+ unset( $this->mConns['foreignFree'][$i][$oldDomain] );
+ $this->mConns['foreignUsed'][$i][$domain] = $conn;
+ $this->connLogger->debug( __METHOD__ .
+ ": reusing free connection from $oldDomain for $domain" );
+ }
+ } else {
+ // Open a new connection
+ $server = $this->mServers[$i];
+ $server['serverIndex'] = $i;
+ $server['foreignPoolRefCount'] = 0;
+ $server['foreign'] = true;
+ $conn = $this->reallyOpenConnection( $server, $dbName );
+ if ( !$conn->isOpen() ) {
+ $this->connLogger->warning( __METHOD__ . ": connection error for $i/$domain" );
+ $this->mErrorConnection = $conn;
+ $conn = false;
+ } else {
+ $conn->tablePrefix( $prefix );
+ $this->mConns['foreignUsed'][$i][$domain] = $conn;
+ $this->connLogger->debug( __METHOD__ . ": opened new connection for $i/$domain" );
+ }
+ }
+
+ // Increment reference count
+ if ( $conn ) {
+ $refCount = $conn->getLBInfo( 'foreignPoolRefCount' );
+ $conn->setLBInfo( 'foreignPoolRefCount', $refCount + 1 );
+ }
+
+ return $conn;
+ }
+
+ /**
+ * Test if the specified index represents an open connection
+ *
+ * @param int $index Server index
+ * @access private
+ * @return bool
+ */
+ private function isOpen( $index ) {
+ if ( !is_integer( $index ) ) {
+ return false;
+ }
+
+ return (bool)$this->getAnyOpenConnection( $index );
+ }
+
+ /**
+ * Really opens a connection. Uncached.
+ * Returns a Database object whether or not the connection was successful.
+ * @access private
+ *
+ * @param array $server
+ * @param bool $dbNameOverride
+ * @return IDatabase
+ * @throws DBAccessError
+ * @throws InvalidArgumentException
+ */
+ protected function reallyOpenConnection( $server, $dbNameOverride = false ) {
+ if ( $this->disabled ) {
+ throw new DBAccessError();
+ }
+
+ if ( !is_array( $server ) ) {
+ throw new InvalidArgumentException(
+ 'You must update your load-balancing configuration. ' .
+ 'See DefaultSettings.php entry for $wgDBservers.' );
+ }
+
+ if ( $dbNameOverride !== false ) {
+ $server['dbname'] = $dbNameOverride;
+ }
+
+ // Let the handle know what the cluster master is (e.g. "db1052")
+ $masterName = $this->getServerName( $this->getWriterIndex() );
+ $server['clusterMasterHost'] = $masterName;
+
+ // Log when many connection are made on requests
+ if ( ++$this->connsOpened >= self::CONN_HELD_WARN_THRESHOLD ) {
+ $this->perfLogger->warning( __METHOD__ . ": " .
+ "{$this->connsOpened}+ connections made (master=$masterName)" );
+ }
+
+ // Set loggers
+ $server['connLogger'] = $this->connLogger;
+ $server['queryLogger'] = $this->queryLogger;
+ $server['trxProfiler'] = $this->trxProfiler;
+
+ // Create a live connection object
+ try {
+ $db = DatabaseBase::factory( $server['type'], $server );
+ } catch ( DBConnectionError $e ) {
+ // FIXME: This is probably the ugliest thing I have ever done to
+ // PHP. I'm half-expecting it to segfault, just out of disgust. -- TS
+ $db = $e->db;
+ }
+
+ $db->setLBInfo( $server );
+ $db->setLazyMasterHandle(
+ $this->getLazyConnectionRef( DB_MASTER, [], $db->getWikiID() )
+ );
+
+ if ( $server['serverIndex'] === $this->getWriterIndex() ) {
+ if ( $this->trxRoundId !== false ) {
+ $this->applyTransactionRoundFlags( $db );
+ }
+ foreach ( $this->trxRecurringCallbacks as $name => $callback ) {
+ $db->setTransactionListener( $name, $callback );
+ }
+ }
+
+ return $db;
+ }
+
+ /**
+ * @throws DBConnectionError
+ * @return bool
+ */
+ private function reportConnectionError() {
+ $conn = $this->mErrorConnection; // The connection which caused the error
+ $context = [
+ 'method' => __METHOD__,
+ 'last_error' => $this->mLastError,
+ ];
+
+ if ( !is_object( $conn ) ) {
+ // No last connection, probably due to all servers being too busy
+ $this->connLogger->error(
+ "LB failure with no last connection. Connection error: {last_error}",
+ $context
+ );
+
+ // If all servers were busy, mLastError will contain something sensible
+ throw new DBConnectionError( null, $this->mLastError );
+ } else {
+ $context['db_server'] = $conn->getProperty( 'mServer' );
+ $this->connLogger->warning(
+ "Connection error: {last_error} ({db_server})",
+ $context
+ );
+
+ // throws DBConnectionError
+ $conn->reportConnectionError( "{$this->mLastError} ({$context['db_server']})" );
+ }
+
+ return false; /* not reached */
+ }
+
+ public function getWriterIndex() {
+ return 0;
+ }
+
+ public function haveIndex( $i ) {
+ return array_key_exists( $i, $this->mServers );
+ }
+
+ public function isNonZeroLoad( $i ) {
+ return array_key_exists( $i, $this->mServers ) && $this->mLoads[$i] != 0;
+ }
+
+ public function getServerCount() {
+ return count( $this->mServers );
+ }
+
+ public function getServerName( $i ) {
+ if ( isset( $this->mServers[$i]['hostName'] ) ) {
+ $name = $this->mServers[$i]['hostName'];
+ } elseif ( isset( $this->mServers[$i]['host'] ) ) {
+ $name = $this->mServers[$i]['host'];
+ } else {
+ $name = '';
+ }
+
+ return ( $name != '' ) ? $name : 'localhost';
+ }
+
+ public function getServerInfo( $i ) {
+ if ( isset( $this->mServers[$i] ) ) {
+ return $this->mServers[$i];
+ } else {
+ return false;
+ }
+ }
+
+ public function setServerInfo( $i, array $serverInfo ) {
+ $this->mServers[$i] = $serverInfo;
+ }
+
+ public function getMasterPos() {
+ # If this entire request was served from a replica DB without opening a connection to the
+ # master (however unlikely that may be), then we can fetch the position from the replica DB.
+ $masterConn = $this->getAnyOpenConnection( $this->getWriterIndex() );
+ if ( !$masterConn ) {
+ $serverCount = count( $this->mServers );
+ for ( $i = 1; $i < $serverCount; $i++ ) {
+ $conn = $this->getAnyOpenConnection( $i );
+ if ( $conn ) {
+ return $conn->getSlavePos();
+ }
+ }
+ } else {
+ return $masterConn->getMasterPos();
+ }
+
+ return false;
+ }
+
+ /**
+ * Disable this load balancer. All connections are closed, and any attempt to
+ * open a new connection will result in a DBAccessError.
+ *
+ * @since 1.27
+ */
+ public function disable() {
+ $this->closeAll();
+ $this->disabled = true;
+ }
+
+ public function closeAll() {
+ $this->forEachOpenConnection( function ( IDatabase $conn ) {
+ $conn->close();
+ } );
+
+ $this->mConns = [
+ 'local' => [],
+ 'foreignFree' => [],
+ 'foreignUsed' => [],
+ ];
+ $this->connsOpened = 0;
+ }
+
+ public function closeConnection( IDatabase $conn ) {
+ $serverIndex = $conn->getLBInfo( 'serverIndex' ); // second index level of mConns
+ foreach ( $this->mConns as $type => $connsByServer ) {
+ if ( !isset( $connsByServer[$serverIndex] ) ) {
+ continue;
+ }
+
+ foreach ( $connsByServer[$serverIndex] as $i => $trackedConn ) {
+ if ( $conn === $trackedConn ) {
+ unset( $this->mConns[$type][$serverIndex][$i] );
+ --$this->connsOpened;
+ break 2;
+ }
+ }
+ }
+
+ $conn->close();
+ }
+
+ public function commitAll( $fname = __METHOD__ ) {
+ $failures = [];
+
+ $restore = ( $this->trxRoundId !== false );
+ $this->trxRoundId = false;
+ $this->forEachOpenConnection(
+ function ( IDatabase $conn ) use ( $fname, $restore, &$failures ) {
+ try {
+ $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
+ } catch ( DBError $e ) {
+ call_user_func( $this->errorLogger, $e );
+ $failures[] = "{$conn->getServer()}: {$e->getMessage()}";
+ }
+ if ( $restore && $conn->getLBInfo( 'master' ) ) {
+ $this->undoTransactionRoundFlags( $conn );
+ }
+ }
+ );
+
+ if ( $failures ) {
+ throw new DBExpectedError(
+ null,
+ "Commit failed on server(s) " . implode( "\n", array_unique( $failures ) )
+ );
+ }
+ }
+
+ /**
+ * Perform all pre-commit callbacks that remain part of the atomic transactions
+ * and disable any post-commit callbacks until runMasterPostTrxCallbacks()
+ * @since 1.28
+ */
+ public function finalizeMasterChanges() {
+ $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) {
+ // Any error should cause all DB transactions to be rolled back together
+ $conn->setTrxEndCallbackSuppression( false );
+ $conn->runOnTransactionPreCommitCallbacks();
+ // Defer post-commit callbacks until COMMIT finishes for all DBs
+ $conn->setTrxEndCallbackSuppression( true );
+ } );
+ }
+
+ /**
+ * Perform all pre-commit checks for things like replication safety
+ * @param array $options Includes:
+ * - maxWriteDuration : max write query duration time in seconds
+ * @throws DBTransactionError
+ * @since 1.28
+ */
+ public function approveMasterChanges( array $options ) {
+ $limit = isset( $options['maxWriteDuration'] ) ? $options['maxWriteDuration'] : 0;
+ $this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( $limit ) {
+ // If atomic sections or explicit transactions are still open, some caller must have
+ // caught an exception but failed to properly rollback any changes. Detect that and
+ // throw and error (causing rollback).
+ if ( $conn->explicitTrxActive() ) {
+ throw new DBTransactionError(
+ $conn,
+ "Explicit transaction still active. A caller may have caught an error."
+ );
+ }
+ // Assert that the time to replicate the transaction will be sane.
+ // If this fails, then all DB transactions will be rollback back together.
+ $time = $conn->pendingWriteQueryDuration( $conn::ESTIMATE_DB_APPLY );
+ if ( $limit > 0 && $time > $limit ) {
+ throw new DBTransactionSizeError(
+ $conn,
+ "Transaction spent $time second(s) in writes, exceeding the $limit limit.",
+ [ $time, $limit ]
+ );
+ }
+ // If a connection sits idle while slow queries execute on another, that connection
+ // may end up dropped before the commit round is reached. Ping servers to detect this.
+ if ( $conn->writesOrCallbacksPending() && !$conn->ping() ) {
+ throw new DBTransactionError(
+ $conn,
+ "A connection to the {$conn->getDBname()} database was lost before commit."
+ );
+ }
+ } );
+ }
+
+ /**
+ * Flush any master transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
+ *
+ * The DBO_TRX setting will be reverted to the default in each of these methods:
+ * - commitMasterChanges()
+ * - rollbackMasterChanges()
+ * - commitAll()
+ * This allows for custom transaction rounds from any outer transaction scope.
+ *
+ * @param string $fname
+ * @throws DBExpectedError
+ * @since 1.28
+ */
+ public function beginMasterChanges( $fname = __METHOD__ ) {
+ if ( $this->trxRoundId !== false ) {
+ throw new DBTransactionError(
+ null,
+ "$fname: Transaction round '{$this->trxRoundId}' already started."
+ );
+ }
+ $this->trxRoundId = $fname;
+
+ $failures = [];
+ $this->forEachOpenMasterConnection(
+ function ( DatabaseBase $conn ) use ( $fname, &$failures ) {
+ $conn->setTrxEndCallbackSuppression( true );
+ try {
+ $conn->flushSnapshot( $fname );
+ } catch ( DBError $e ) {
+ call_user_func( $this->errorLogger, $e );
+ $failures[] = "{$conn->getServer()}: {$e->getMessage()}";
+ }
+ $conn->setTrxEndCallbackSuppression( false );
+ $this->applyTransactionRoundFlags( $conn );
+ }
+ );
+
+ if ( $failures ) {
+ throw new DBExpectedError(
+ null,
+ "$fname: Flush failed on server(s) " . implode( "\n", array_unique( $failures ) )
+ );
+ }
+ }
+
+ public function commitMasterChanges( $fname = __METHOD__ ) {
+ $failures = [];
+
+ $restore = ( $this->trxRoundId !== false );
+ $this->trxRoundId = false;
+ $this->forEachOpenMasterConnection(
+ function ( IDatabase $conn ) use ( $fname, $restore, &$failures ) {
+ try {
+ if ( $conn->writesOrCallbacksPending() ) {
+ $conn->commit( $fname, $conn::FLUSHING_ALL_PEERS );
+ } elseif ( $restore ) {
+ $conn->flushSnapshot( $fname );
+ }
+ } catch ( DBError $e ) {
+ call_user_func( $this->errorLogger, $e );
+ $failures[] = "{$conn->getServer()}: {$e->getMessage()}";
+ }
+ if ( $restore ) {
+ $this->undoTransactionRoundFlags( $conn );
+ }
+ }
+ );
+
+ if ( $failures ) {
+ throw new DBExpectedError(
+ null,
+ "$fname: Commit failed on server(s) " . implode( "\n", array_unique( $failures ) )
+ );
+ }
+ }
+
+ /**
+ * Issue all pending post-COMMIT/ROLLBACK callbacks
+ * @param integer $type IDatabase::TRIGGER_* constant
+ * @return Exception|null The first exception or null if there were none
+ * @since 1.28
+ */
+ public function runMasterPostTrxCallbacks( $type ) {
+ $e = null; // first exception
+ $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) use ( $type, &$e ) {
+ $conn->setTrxEndCallbackSuppression( false );
+ if ( $conn->writesOrCallbacksPending() ) {
+ // This happens if onTransactionIdle() callbacks leave callbacks on *another* DB
+ // (which finished its callbacks already). Warn and recover in this case. Let the
+ // callbacks run in the final commitMasterChanges() in LBFactory::shutdown().
+ $this->queryLogger->error( __METHOD__ . ": found writes/callbacks pending." );
+ return;
+ } elseif ( $conn->trxLevel() ) {
+ // This happens for single-DB setups where DB_REPLICA uses the master DB,
+ // thus leaving an implicit read-only transaction open at this point. It
+ // also happens if onTransactionIdle() callbacks leave implicit transactions
+ // open on *other* DBs (which is slightly improper). Let these COMMIT on the
+ // next call to commitMasterChanges(), possibly in LBFactory::shutdown().
+ return;
+ }
+ try {
+ $conn->runOnTransactionIdleCallbacks( $type );
+ } catch ( Exception $ex ) {
+ $e = $e ?: $ex;
+ }
+ try {
+ $conn->runTransactionListenerCallbacks( $type );
+ } catch ( Exception $ex ) {
+ $e = $e ?: $ex;
+ }
+ } );
+
+ return $e;
+ }
+
+ /**
+ * Issue ROLLBACK only on master, only if queries were done on connection
+ * @param string $fname Caller name
+ * @throws DBExpectedError
+ * @since 1.23
+ */
+ public function rollbackMasterChanges( $fname = __METHOD__ ) {
+ $restore = ( $this->trxRoundId !== false );
+ $this->trxRoundId = false;
+ $this->forEachOpenMasterConnection(
+ function ( IDatabase $conn ) use ( $fname, $restore ) {
+ if ( $conn->writesOrCallbacksPending() ) {
+ $conn->rollback( $fname, $conn::FLUSHING_ALL_PEERS );
+ }
+ if ( $restore ) {
+ $this->undoTransactionRoundFlags( $conn );
+ }
+ }
+ );
+ }
+
+ /**
+ * Suppress all pending post-COMMIT/ROLLBACK callbacks
+ * @return Exception|null The first exception or null if there were none
+ * @since 1.28
+ */
+ public function suppressTransactionEndCallbacks() {
+ $this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) {
+ $conn->setTrxEndCallbackSuppression( true );
+ } );
+ }
+
+ /**
+ * @param IDatabase $conn
+ */
+ private function applyTransactionRoundFlags( IDatabase $conn ) {
+ if ( $conn->getFlag( DBO_DEFAULT ) ) {
+ // DBO_TRX is controlled entirely by CLI mode presence with DBO_DEFAULT.
+ // Force DBO_TRX even in CLI mode since a commit round is expected soon.
+ $conn->setFlag( DBO_TRX, $conn::REMEMBER_PRIOR );
+ // If config has explicitly requested DBO_TRX be either on or off by not
+ // setting DBO_DEFAULT, then respect that. Forcing no transactions is useful
+ // for things like blob stores (ExternalStore) which want auto-commit mode.
+ }
+ }
+
+ /**
+ * @param IDatabase $conn
+ */
+ private function undoTransactionRoundFlags( IDatabase $conn ) {
+ if ( $conn->getFlag( DBO_DEFAULT ) ) {
+ $conn->restoreFlags( $conn::RESTORE_PRIOR );
+ }
+ }
+
+ /**
+ * Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot
+ *
+ * @param string $fname Caller name
+ * @since 1.28
+ */
+ public function flushReplicaSnapshots( $fname = __METHOD__ ) {
+ $this->forEachOpenReplicaConnection( function ( IDatabase $conn ) {
+ $conn->flushSnapshot( __METHOD__ );
+ } );
+ }
+
+ /**
+ * @return bool Whether a master connection is already open
+ * @since 1.24
+ */
+ public function hasMasterConnection() {
+ return $this->isOpen( $this->getWriterIndex() );
+ }
+
+ /**
+ * Determine if there are pending changes in a transaction by this thread
+ * @since 1.23
+ * @return bool
+ */
+ public function hasMasterChanges() {
+ $pending = 0;
+ $this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( &$pending ) {
+ $pending |= $conn->writesOrCallbacksPending();
+ } );
+
+ return (bool)$pending;
+ }
+
+ /**
+ * Get the timestamp of the latest write query done by this thread
+ * @since 1.25
+ * @return float|bool UNIX timestamp or false
+ */
+ public function lastMasterChangeTimestamp() {
+ $lastTime = false;
+ $this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( &$lastTime ) {
+ $lastTime = max( $lastTime, $conn->lastDoneWrites() );
+ } );
+
+ return $lastTime;
+ }
+
+ /**
+ * Check if this load balancer object had any recent or still
+ * pending writes issued against it by this PHP thread
+ *
+ * @param float $age How many seconds ago is "recent" [defaults to mWaitTimeout]
+ * @return bool
+ * @since 1.25
+ */
+ public function hasOrMadeRecentMasterChanges( $age = null ) {
+ $age = ( $age === null ) ? $this->mWaitTimeout : $age;
+
+ return ( $this->hasMasterChanges()
+ || $this->lastMasterChangeTimestamp() > microtime( true ) - $age );
+ }
+
+ /**
+ * Get the list of callers that have pending master changes
+ *
+ * @return string[] List of method names
+ * @since 1.27
+ */
+ public function pendingMasterChangeCallers() {
+ $fnames = [];
+ $this->forEachOpenMasterConnection( function ( IDatabase $conn ) use ( &$fnames ) {
+ $fnames = array_merge( $fnames, $conn->pendingWriteCallers() );
+ } );
+
+ return $fnames;
+ }
+
+ public function getLaggedReplicaMode( $domain = false ) {
+ // No-op if there is only one DB (also avoids recursion)
+ if ( !$this->laggedReplicaMode && $this->getServerCount() > 1 ) {
+ try {
+ // See if laggedReplicaMode gets set
+ $conn = $this->getConnection( DB_REPLICA, false, $domain );
+ $this->reuseConnection( $conn );
+ } catch ( DBConnectionError $e ) {
+ // Avoid expensive re-connect attempts and failures
+ $this->allReplicasDownMode = true;
+ $this->laggedReplicaMode = true;
+ }
+ }
+
+ return $this->laggedReplicaMode;
+ }
+
+ /**
+ * @param bool $domain
+ * @return bool
+ * @deprecated 1.28; use getLaggedReplicaMode()
+ */
+ public function getLaggedSlaveMode( $domain = false ) {
+ return $this->getLaggedReplicaMode( $domain );
+ }
+
+ /**
+ * @note This method will never cause a new DB connection
+ * @return bool Whether any generic connection used for reads was highly "lagged"
+ * @since 1.28
+ */
+ public function laggedReplicaUsed() {
+ return $this->laggedReplicaMode;
+ }
+
+ /**
+ * @return bool
+ * @since 1.27
+ * @deprecated Since 1.28; use laggedReplicaUsed()
+ */
+ public function laggedSlaveUsed() {
+ return $this->laggedReplicaUsed();
+ }
+
+ /**
+ * @note This method may trigger a DB connection if not yet done
+ * @param string|bool $domain Domain ID, or false for the current domain
+ * @param IDatabase|null DB master connection; used to avoid loops [optional]
+ * @return string|bool Reason the master is read-only or false if it is not
+ * @since 1.27
+ */
+ public function getReadOnlyReason( $domain = false, IDatabase $conn = null ) {
+ if ( $this->readOnlyReason !== false ) {
+ return $this->readOnlyReason;
+ } elseif ( $this->getLaggedReplicaMode( $domain ) ) {
+ if ( $this->allReplicasDownMode ) {
+ return 'The database has been automatically locked ' .
+ 'until the replica database servers become available';
+ } else {
+ return 'The database has been automatically locked ' .
+ 'while the replica database servers catch up to the master.';
+ }
+ } elseif ( $this->masterRunningReadOnly( $domain, $conn ) ) {
+ return 'The database master is running in read-only mode.';
+ }
+
+ return false;
+ }
+
+ /**
+ * @param string $domain Domain ID, or false for the current domain
+ * @param IDatabase|null DB master connectionl used to avoid loops [optional]
+ * @return bool
+ */
+ private function masterRunningReadOnly( $domain, IDatabase $conn = null ) {
+ $cache = $this->wanCache;
+ $masterServer = $this->getServerName( $this->getWriterIndex() );
+
+ return (bool)$cache->getWithSetCallback(
+ $cache->makeGlobalKey( __CLASS__, 'server-read-only', $masterServer ),
+ self::TTL_CACHE_READONLY,
+ function () use ( $domain, $conn ) {
+ $this->trxProfiler->setSilenced( true );
+ try {
+ $dbw = $conn ?: $this->getConnection( DB_MASTER, [], $domain );
+ $readOnly = (int)$dbw->serverIsReadOnly();
+ } catch ( DBError $e ) {
+ $readOnly = 0;
+ }
+ $this->trxProfiler->setSilenced( false );
+ return $readOnly;
+ },
+ [ 'pcTTL' => $cache::TTL_PROC_LONG, 'busyValue' => 0 ]
+ );
+ }
+
+ public function allowLagged( $mode = null ) {
+ if ( $mode === null ) {
+ return $this->mAllowLagged;
+ }
+ $this->mAllowLagged = $mode;
+
+ return $this->mAllowLagged;
+ }
+
+ public function pingAll() {
+ $success = true;
+ $this->forEachOpenConnection( function ( IDatabase $conn ) use ( &$success ) {
+ if ( !$conn->ping() ) {
+ $success = false;
+ }
+ } );
+
+ return $success;
+ }
+
+ public function forEachOpenConnection( $callback, array $params = [] ) {
+ foreach ( $this->mConns as $connsByServer ) {
+ foreach ( $connsByServer as $serverConns ) {
+ foreach ( $serverConns as $conn ) {
+ $mergedParams = array_merge( [ $conn ], $params );
+ call_user_func_array( $callback, $mergedParams );
+ }
+ }
+ }
+ }
+
+ /**
+ * Call a function with each open connection object to a master
+ * @param callable $callback
+ * @param array $params
+ * @since 1.28
+ */
+ public function forEachOpenMasterConnection( $callback, array $params = [] ) {
+ $masterIndex = $this->getWriterIndex();
+ foreach ( $this->mConns as $connsByServer ) {
+ if ( isset( $connsByServer[$masterIndex] ) ) {
+ /** @var IDatabase $conn */
+ foreach ( $connsByServer[$masterIndex] as $conn ) {
+ $mergedParams = array_merge( [ $conn ], $params );
+ call_user_func_array( $callback, $mergedParams );
+ }
+ }
+ }
+ }
+
+ /**
+ * Call a function with each open replica DB connection object
+ * @param callable $callback
+ * @param array $params
+ * @since 1.28
+ */
+ public function forEachOpenReplicaConnection( $callback, array $params = [] ) {
+ foreach ( $this->mConns as $connsByServer ) {
+ foreach ( $connsByServer as $i => $serverConns ) {
+ if ( $i === $this->getWriterIndex() ) {
+ continue; // skip master
+ }
+ foreach ( $serverConns as $conn ) {
+ $mergedParams = array_merge( [ $conn ], $params );
+ call_user_func_array( $callback, $mergedParams );
+ }
+ }
+ }
+ }
+
+ public function getMaxLag( $domain = false ) {
+ $maxLag = -1;
+ $host = '';
+ $maxIndex = 0;
+
+ if ( $this->getServerCount() <= 1 ) {
+ return [ $host, $maxLag, $maxIndex ]; // no replication = no lag
+ }
+
+ $lagTimes = $this->getLagTimes( $domain );
+ foreach ( $lagTimes as $i => $lag ) {
+ if ( $this->mLoads[$i] > 0 && $lag > $maxLag ) {
+ $maxLag = $lag;
+ $host = $this->mServers[$i]['host'];
+ $maxIndex = $i;
+ }
+ }
+
+ return [ $host, $maxLag, $maxIndex ];
+ }
+
+ public function getLagTimes( $domain = false ) {
+ if ( $this->getServerCount() <= 1 ) {
+ return [ 0 => 0 ]; // no replication = no lag
+ }
+
+ # Send the request to the load monitor
+ return $this->getLoadMonitor()->getLagTimes( array_keys( $this->mServers ), $domain );
+ }
+
+ public function safeGetLag( IDatabase $conn ) {
+ if ( $this->getServerCount() == 1 ) {
+ return 0;
+ } else {
+ return $conn->getLag();
+ }
+ }
+
+ /**
+ * Wait for a replica DB to reach a specified master position
+ *
+ * This will connect to the master to get an accurate position if $pos is not given
+ *
+ * @param IDatabase $conn Replica DB
+ * @param DBMasterPos|bool $pos Master position; default: current position
+ * @param integer $timeout Timeout in seconds
+ * @return bool Success
+ * @since 1.27
+ */
+ public function safeWaitForMasterPos( IDatabase $conn, $pos = false, $timeout = 10 ) {
+ if ( $this->getServerCount() == 1 || !$conn->getLBInfo( 'replica' ) ) {
+ return true; // server is not a replica DB
+ }
+
+ $pos = $pos ?: $this->getConnection( DB_MASTER )->getMasterPos();
+ if ( !( $pos instanceof DBMasterPos ) ) {
+ return false; // something is misconfigured
+ }
+
+ $result = $conn->masterPosWait( $pos, $timeout );
+ if ( $result == -1 || is_null( $result ) ) {
+ $msg = __METHOD__ . ": Timed out waiting on {$conn->getServer()} pos {$pos}";
+ $this->replLogger->warning( "$msg" );
+ $ok = false;
+ } else {
+ $this->replLogger->info( __METHOD__ . ": Done" );
+ $ok = true;
+ }
+
+ return $ok;
+ }
+
+ /**
+ * Clear the cache for slag lag delay times
+ *
+ * This is only used for testing
+ * @since 1.26
+ */
+ public function clearLagTimeCache() {
+ $this->getLoadMonitor()->clearCaches();
+ }
+
+ /**
+ * Set a callback via IDatabase::setTransactionListener() on
+ * all current and future master connections of this load balancer
+ *
+ * @param string $name Callback name
+ * @param callable|null $callback
+ * @since 1.28
+ */
+ public function setTransactionListener( $name, callable $callback = null ) {
+ if ( $callback ) {
+ $this->trxRecurringCallbacks[$name] = $callback;
+ } else {
+ unset( $this->trxRecurringCallbacks[$name] );
+ }
+ $this->forEachOpenMasterConnection(
+ function ( IDatabase $conn ) use ( $name, $callback ) {
+ $conn->setTransactionListener( $name, $callback );
+ }
+ );
+ }
+
+ /**
+ * Set a new table prefix for the existing local domain ID for testing
+ *
+ * @param string $prefix
+ * @since 1.28
+ */
+ public function setDomainPrefix( $prefix ) {
+ list( $dbName, ) = explode( '-', $this->localDomain, 2 );
+
+ $this->localDomain = "{$dbName}-{$prefix}";
+ }
+}
--- /dev/null
+<?php
+/**
+ * Database load monitoring.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Database
+ */
+use Psr\Log\LoggerAwareInterface;
+
+/**
+ * An interface for database load monitoring
+ *
+ * @ingroup Database
+ */
+interface LoadMonitor extends LoggerAwareInterface {
+ /**
+ * Construct a new LoadMonitor with a given LoadBalancer parent
+ *
+ * @param ILoadBalancer $lb LoadBalancer this instance serves
+ * @param BagOStuff $sCache Local server memory cache
+ * @param BagOStuff $cCache Local cluster memory cache
+ */
+ public function __construct( ILoadBalancer $lb, BagOStuff $sCache, BagOStuff $cCache );
+
+ /**
+ * Perform pre-connection load ratio adjustment.
+ * @param int[] &$loads
+ * @param string|bool $group The selected query group. Default: false
+ * @param string|bool $domain Default: false
+ */
+ public function scaleLoads( &$loads, $group = false, $domain = false );
+
+ /**
+ * Get an estimate of replication lag (in seconds) for each server
+ *
+ * Values may be "false" if replication is too broken to estimate
+ *
+ * @param integer[] $serverIndexes
+ * @param string $domain
+ *
+ * @return array Map of (server index => float|int|bool)
+ */
+ public function getLagTimes( $serverIndexes, $domain );
+
+ /**
+ * Clear any process and persistent cache of lag times
+ * @since 1.27
+ */
+ public function clearCaches();
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Database
+ */
+
+use Psr\Log\LoggerInterface;
+
+/**
+ * Basic MySQL load monitor with no external dependencies
+ * Uses memcached to cache the replication lag for a short time
+ *
+ * @ingroup Database
+ */
+class LoadMonitorMySQL implements LoadMonitor {
+ /** @var ILoadBalancer */
+ protected $parent;
+ /** @var BagOStuff */
+ protected $srvCache;
+ /** @var BagOStuff */
+ protected $mainCache;
+ /** @var LoggerInterface */
+ protected $replLogger;
+
+ public function __construct( ILoadBalancer $lb, BagOStuff $srvCache, BagOStuff $cache ) {
+ $this->parent = $lb;
+ $this->srvCache = $srvCache;
+ $this->mainCache = $cache;
+ $this->replLogger = new \Psr\Log\NullLogger();
+ }
+
+ public function setLogger( LoggerInterface $logger ) {
+ $this->replLogger = $logger;
+ }
+
+ public function scaleLoads( &$loads, $group = false, $wiki = false ) {
+ }
+
+ public function getLagTimes( $serverIndexes, $wiki ) {
+ if ( count( $serverIndexes ) == 1 && reset( $serverIndexes ) == 0 ) {
+ # Single server only, just return zero without caching
+ return [ 0 => 0 ];
+ }
+
+ $key = $this->getLagTimeCacheKey();
+ # Randomize TTLs to reduce stampedes (4.0 - 5.0 sec)
+ $ttl = mt_rand( 4e6, 5e6 ) / 1e6;
+ # Keep keys around longer as fallbacks
+ $staleTTL = 60;
+
+ # (a) Check the local APC cache
+ $value = $this->srvCache->get( $key );
+ if ( $value && $value['timestamp'] > ( microtime( true ) - $ttl ) ) {
+ $this->replLogger->debug( __METHOD__ . ": got lag times ($key) from local cache" );
+ return $value['lagTimes']; // cache hit
+ }
+ $staleValue = $value ?: false;
+
+ # (b) Check the shared cache and backfill APC
+ $value = $this->mainCache->get( $key );
+ if ( $value && $value['timestamp'] > ( microtime( true ) - $ttl ) ) {
+ $this->srvCache->set( $key, $value, $staleTTL );
+ $this->replLogger->debug( __METHOD__ . ": got lag times ($key) from main cache" );
+
+ return $value['lagTimes']; // cache hit
+ }
+ $staleValue = $value ?: $staleValue;
+
+ # (c) Cache key missing or expired; regenerate and backfill
+ if ( $this->mainCache->lock( $key, 0, 10 ) ) {
+ # Let this process alone update the cache value
+ $cache = $this->mainCache;
+ /** @noinspection PhpUnusedLocalVariableInspection */
+ $unlocker = new ScopedCallback( function () use ( $cache, $key ) {
+ $cache->unlock( $key );
+ } );
+ } elseif ( $staleValue ) {
+ # Could not acquire lock but an old cache exists, so use it
+ return $staleValue['lagTimes'];
+ }
+
+ $lagTimes = [];
+ foreach ( $serverIndexes as $i ) {
+ if ( $i == $this->parent->getWriterIndex() ) {
+ $lagTimes[$i] = 0; // master always has no lag
+ continue;
+ }
+
+ $conn = $this->parent->getAnyOpenConnection( $i );
+ if ( $conn ) {
+ $close = false; // already open
+ } else {
+ $conn = $this->parent->openConnection( $i, $wiki );
+ $close = true; // new connection
+ }
+
+ if ( !$conn ) {
+ $lagTimes[$i] = false;
+ $host = $this->parent->getServerName( $i );
+ $this->replLogger->error( __METHOD__ . ": host $host (#$i) is unreachable" );
+ continue;
+ }
+
+ $lagTimes[$i] = $conn->getLag();
+ if ( $lagTimes[$i] === false ) {
+ $host = $this->parent->getServerName( $i );
+ $this->replLogger->error( __METHOD__ . ": host $host (#$i) is not replicating?" );
+ }
+
+ if ( $close ) {
+ # Close the connection to avoid sleeper connections piling up.
+ # Note that the caller will pick one of these DBs and reconnect,
+ # which is slightly inefficient, but this only matters for the lag
+ # time cache miss cache, which is far less common that cache hits.
+ $this->parent->closeConnection( $conn );
+ }
+ }
+
+ # Add a timestamp key so we know when it was cached
+ $value = [ 'lagTimes' => $lagTimes, 'timestamp' => microtime( true ) ];
+ $this->mainCache->set( $key, $value, $staleTTL );
+ $this->srvCache->set( $key, $value, $staleTTL );
+ $this->replLogger->info( __METHOD__ . ": re-calculated lag times ($key)" );
+
+ return $value['lagTimes'];
+ }
+
+ public function clearCaches() {
+ $key = $this->getLagTimeCacheKey();
+ $this->srvCache->delete( $key );
+ $this->mainCache->delete( $key );
+ }
+
+ private function getLagTimeCacheKey() {
+ $writerIndex = $this->parent->getWriterIndex();
+ // Lag is per-server, not per-DB, so key on the master DB name
+ return $this->srvCache->makeGlobalKey(
+ 'lag-times',
+ $this->parent->getServerName( $writerIndex )
+ );
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Database
+ */
+use Psr\Log\LoggerInterface;
+
+class LoadMonitorNull implements LoadMonitor {
+ public function __construct( ILoadBalancer $lb, BagOStuff $sCache, BagOStuff $cCache ) {
+
+ }
+
+ public function setLogger( LoggerInterface $logger ) {
+ }
+
+ public function scaleLoads( &$loads, $group = false, $wiki = false ) {
+
+ }
+
+ public function getLagTimes( $serverIndexes, $wiki ) {
+ return array_fill_keys( $serverIndexes, 0 );
+ }
+
+ public function clearCaches() {
+
+ }
+}
+++ /dev/null
-<?php
-/**
- * Object caching using memcached.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Cache
- */
-
-/**
- * A wrapper class for the PECL memcached client
- *
- * @ingroup Cache
- */
-class MemcachedPeclBagOStuff extends MemcachedBagOStuff {
-
- /**
- * Constructor
- *
- * Available parameters are:
- * - servers: The list of IP:port combinations holding the memcached servers.
- * - persistent: Whether to use a persistent connection
- * - compress_threshold: The minimum size an object must be before it is compressed
- * - timeout: The read timeout in microseconds
- * - connect_timeout: The connect timeout in seconds
- * - retry_timeout: Time in seconds to wait before retrying a failed connect attempt
- * - server_failure_limit: Limit for server connect failures before it is removed
- * - serializer: May be either "php" or "igbinary". Igbinary produces more compact
- * values, but serialization is much slower unless the php.ini option
- * igbinary.compact_strings is off.
- * - use_binary_protocol Whether to enable the binary protocol (default is ASCII) (boolean)
- * @param array $params
- * @throws InvalidArgumentException
- */
- function __construct( $params ) {
- parent::__construct( $params );
- $params = $this->applyDefaultParams( $params );
-
- if ( $params['persistent'] ) {
- // The pool ID must be unique to the server/option combination.
- // The Memcached object is essentially shared for each pool ID.
- // We can only reuse a pool ID if we keep the config consistent.
- $this->client = new Memcached( md5( serialize( $params ) ) );
- if ( count( $this->client->getServerList() ) ) {
- $this->logger->debug( __METHOD__ . ": persistent Memcached object already loaded." );
- return; // already initialized; don't add duplicate servers
- }
- } else {
- $this->client = new Memcached;
- }
-
- if ( $params['use_binary_protocol'] ) {
- $this->client->setOption( Memcached::OPT_BINARY_PROTOCOL, true );
- }
-
- if ( isset( $params['retry_timeout'] ) ) {
- $this->client->setOption( Memcached::OPT_RETRY_TIMEOUT, $params['retry_timeout'] );
- }
-
- if ( isset( $params['server_failure_limit'] ) ) {
- $this->client->setOption( Memcached::OPT_SERVER_FAILURE_LIMIT, $params['server_failure_limit'] );
- }
-
- // The compression threshold is an undocumented php.ini option for some
- // reason. There's probably not much harm in setting it globally, for
- // compatibility with the settings for the PHP client.
- ini_set( 'memcached.compression_threshold', $params['compress_threshold'] );
-
- // Set timeouts
- $this->client->setOption( Memcached::OPT_CONNECT_TIMEOUT, $params['connect_timeout'] * 1000 );
- $this->client->setOption( Memcached::OPT_SEND_TIMEOUT, $params['timeout'] );
- $this->client->setOption( Memcached::OPT_RECV_TIMEOUT, $params['timeout'] );
- $this->client->setOption( Memcached::OPT_POLL_TIMEOUT, $params['timeout'] / 1000 );
-
- // Set libketama mode since it's recommended by the documentation and
- // is as good as any. There's no way to configure libmemcached to use
- // hashes identical to the ones currently in use by the PHP client, and
- // even implementing one of the libmemcached hashes in pure PHP for
- // forwards compatibility would require MemcachedClient::get_sock() to be
- // rewritten.
- $this->client->setOption( Memcached::OPT_LIBKETAMA_COMPATIBLE, true );
-
- // Set the serializer
- switch ( $params['serializer'] ) {
- case 'php':
- $this->client->setOption( Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_PHP );
- break;
- case 'igbinary':
- if ( !Memcached::HAVE_IGBINARY ) {
- throw new InvalidArgumentException(
- __CLASS__ . ': the igbinary extension is not available ' .
- 'but igbinary serialization was requested.'
- );
- }
- $this->client->setOption( Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_IGBINARY );
- break;
- default:
- throw new InvalidArgumentException(
- __CLASS__ . ': invalid value for serializer parameter'
- );
- }
- $servers = [];
- foreach ( $params['servers'] as $host ) {
- $servers[] = IP::splitHostAndPort( $host ); // (ip, port)
- }
- $this->client->addServers( $servers );
- }
-
- protected function applyDefaultParams( $params ) {
- $params = parent::applyDefaultParams( $params );
-
- if ( !isset( $params['use_binary_protocol'] ) ) {
- $params['use_binary_protocol'] = false;
- }
-
- if ( !isset( $params['serializer'] ) ) {
- $params['serializer'] = 'php';
- }
-
- return $params;
- }
-
- protected function getWithToken( $key, &$casToken, $flags = 0 ) {
- $this->debugLog( "get($key)" );
- $result = $this->client->get( $this->validateKeyEncoding( $key ), null, $casToken );
- $result = $this->checkResult( $key, $result );
- return $result;
- }
-
- public function set( $key, $value, $exptime = 0, $flags = 0 ) {
- $this->debugLog( "set($key)" );
- return $this->checkResult( $key, parent::set( $key, $value, $exptime ) );
- }
-
- protected function cas( $casToken, $key, $value, $exptime = 0 ) {
- $this->debugLog( "cas($key)" );
- return $this->checkResult( $key, parent::cas( $casToken, $key, $value, $exptime ) );
- }
-
- public function delete( $key ) {
- $this->debugLog( "delete($key)" );
- $result = parent::delete( $key );
- if ( $result === false && $this->client->getResultCode() === Memcached::RES_NOTFOUND ) {
- // "Not found" is counted as success in our interface
- return true;
- } else {
- return $this->checkResult( $key, $result );
- }
- }
-
- public function add( $key, $value, $exptime = 0 ) {
- $this->debugLog( "add($key)" );
- return $this->checkResult( $key, parent::add( $key, $value, $exptime ) );
- }
-
- public function incr( $key, $value = 1 ) {
- $this->debugLog( "incr($key)" );
- $result = $this->client->increment( $key, $value );
- return $this->checkResult( $key, $result );
- }
-
- public function decr( $key, $value = 1 ) {
- $this->debugLog( "decr($key)" );
- $result = $this->client->decrement( $key, $value );
- return $this->checkResult( $key, $result );
- }
-
- /**
- * Check the return value from a client method call and take any necessary
- * action. Returns the value that the wrapper function should return. At
- * present, the return value is always the same as the return value from
- * the client, but some day we might find a case where it should be
- * different.
- *
- * @param string $key The key used by the caller, or false if there wasn't one.
- * @param mixed $result The return value
- * @return mixed
- */
- protected function checkResult( $key, $result ) {
- if ( $result !== false ) {
- return $result;
- }
- switch ( $this->client->getResultCode() ) {
- case Memcached::RES_SUCCESS:
- break;
- case Memcached::RES_DATA_EXISTS:
- case Memcached::RES_NOTSTORED:
- case Memcached::RES_NOTFOUND:
- $this->debugLog( "result: " . $this->client->getResultMessage() );
- break;
- default:
- $msg = $this->client->getResultMessage();
- $logCtx = [];
- if ( $key !== false ) {
- $server = $this->client->getServerByKey( $key );
- $logCtx['memcached-server'] = "{$server['host']}:{$server['port']}";
- $logCtx['memcached-key'] = $key;
- $msg = "Memcached error for key \"{memcached-key}\" on server \"{memcached-server}\": $msg";
- } else {
- $msg = "Memcached error: $msg";
- }
- $this->logger->error( $msg, $logCtx );
- $this->setLastError( BagOStuff::ERR_UNEXPECTED );
- }
- return $result;
- }
-
- public function getMulti( array $keys, $flags = 0 ) {
- $this->debugLog( 'getMulti(' . implode( ', ', $keys ) . ')' );
- foreach ( $keys as $key ) {
- $this->validateKeyEncoding( $key );
- }
- $result = $this->client->getMulti( $keys ) ?: [];
- return $this->checkResult( false, $result );
- }
-
- /**
- * @param array $data
- * @param int $exptime
- * @return bool
- */
- public function setMulti( array $data, $exptime = 0 ) {
- $this->debugLog( 'setMulti(' . implode( ', ', array_keys( $data ) ) . ')' );
- foreach ( array_keys( $data ) as $key ) {
- $this->validateKeyEncoding( $key );
- }
- $result = $this->client->setMulti( $data, $this->fixExpiry( $exptime ) );
- return $this->checkResult( false, $result );
- }
-
- public function changeTTL( $key, $expiry = 0 ) {
- $this->debugLog( "touch($key)" );
- $result = $this->client->touch( $key, $expiry );
- return $this->checkResult( $key, $result );
- }
-}
} else {
$this->automaticFailover = true;
}
+
+ $this->attrMap[self::ATTR_SYNCWRITES] = self::QOS_SYNCWRITES_NONE;
}
protected function doGet( $key, $flags = 0 ) {
parent::__construct( $params );
$this->attrMap[self::ATTR_EMULATION] = self::QOS_EMULATION_SQL;
+ $this->attrMap[self::ATTR_SYNCWRITES] = self::QOS_SYNCWRITES_NONE;
if ( isset( $params['servers'] ) ) {
$this->serverInfos = [];
// Default to using the main wiki's database servers
$this->serverInfos = false;
$this->numServers = 1;
+ $this->attrMap[self::ATTR_SYNCWRITES] = self::QOS_SYNCWRITES_BE;
}
if ( isset( $params['purgePeriod'] ) ) {
$this->purgePeriod = intval( $params['purgePeriod'] );
*/
public static function newFromID( $id ) {
$t = Title::newFromID( $id );
- # @todo FIXME: Doesn't inherit right
- return $t == null ? null : new self( $t );
- # return $t == null ? null : new static( $t ); // PHP 5.3
+ return $t == null ? null : new static( $t );
}
/**
* Call to WikiPage function for backwards compatibility.
* @see WikiPage::doPurge
*/
- public function doPurge() {
- return $this->mPage->doPurge();
+ public function doPurge( $flags = WikiPage::PURGE_ALL ) {
+ return $this->mPage->doPurge( $flags );
+ }
+
+ /**
+ * Call to WikiPage function for backwards compatibility.
+ * @see WikiPage::getLastPurgeTimestamp
+ */
+ public function getLastPurgeTimestamp() {
+ return $this->mPage->getLastPurgeTimestamp();
}
/**
return new WikiCategoryPage( $title );
}
- /**
- * Constructor from a page id
- * @param int $id Article ID to load
- * @return CategoryPage|null
- */
- public static function newFromID( $id ) {
- $t = Title::newFromID( $id );
- # @todo FIXME: Doesn't inherit right
- return $t == null ? null : new self( $t );
- # return $t == null ? null : new static( $t ); // PHP 5.3
- }
-
function view() {
$request = $this->getContext()->getRequest();
$diff = $request->getVal( 'diff' );
return new WikiFilePage( $title );
}
- /**
- * Constructor from a page id
- * @param int $id Article ID to load
- * @return ImagePage|null
- */
- public static function newFromID( $id ) {
- $t = Title::newFromID( $id );
- # @todo FIXME: Doesn't inherit right
- return $t == null ? null : new self( $t );
- # return $t == null ? null : new static( $t ); // PHP 5.3
- }
-
/**
* @param File $file
* @return void
return $this->mDupes;
}
- /**
- * Override handling of action=purge
- * @return bool
- */
- public function doPurge() {
+ public function doPurge( $flags = self::PURGE_ALL ) {
$this->loadFile();
+
if ( $this->mFile->exists() ) {
wfDebug( 'ImagePage::doPurge purging ' . $this->mFile->getName() . "\n" );
DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this->mTitle, 'imagelinks' ) );
// Purge redirect cache
$this->mRepo->invalidateImageRedirect( $this->mTitle );
}
- return parent::doPurge();
+
+ return parent::doPurge( $flags );
}
/**
*/
protected $mLinksUpdated = '19700101000000';
+ const PURGE_CDN_CACHE = 1; // purge CDN cache for page variant URLs
+ const PURGE_CLUSTER_PCACHE = 2; // purge parser cache in the local datacenter
+ const PURGE_GLOBAL_PCACHE = 4; // set page_touched to clear parser cache in all datacenters
+ const PURGE_ALL = 7;
+
/**
* Constructor and clear the article
* @param Title $title Reference to a Title object.
* @return bool
*/
public function hasViewableContent() {
- return $this->exists() || $this->mTitle->isAlwaysKnown();
+ return $this->mTitle->isKnown();
}
/**
*/
public function getContentModel() {
if ( $this->exists() ) {
- // look at the revision's actual content model
- $rev = $this->getRevision();
-
- if ( $rev !== null ) {
- return $rev->getContentModel();
- } else {
- $title = $this->mTitle->getPrefixedDBkey();
- wfWarn( "Page $title exists but has no (visible) revisions!" );
- }
+ $cache = ObjectCache::getMainWANInstance();
+
+ return $cache->getWithSetCallback(
+ $cache->makeKey( 'page', 'content-model', $this->getLatest() ),
+ $cache::TTL_MONTH,
+ function () {
+ $rev = $this->getRevision();
+ if ( $rev ) {
+ // Look at the revision's actual content model
+ return $rev->getContentModel();
+ } else {
+ $title = $this->mTitle->getPrefixedDBkey();
+ wfWarn( "Page $title exists but has no (visible) revisions!" );
+ return $this->mTitle->getContentModel();
+ }
+ }
+ );
}
// use the default model for this page
// happened after the first S1 SELECT.
// http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read
$flags = Revision::READ_LOCKING;
+ $revision = Revision::newFromPageId( $this->getId(), $latest, $flags );
} elseif ( $this->mDataLoadedFrom == self::READ_LATEST ) {
// Bug T93976: if page_latest was loaded from the master, fetch the
// revision from there as well, as it may not exist yet on a replica DB.
// Also, this keeps the queries in the same REPEATABLE-READ snapshot.
$flags = Revision::READ_LATEST;
+ $revision = Revision::newFromPageId( $this->getId(), $latest, $flags );
} else {
- $flags = 0;
+ $dbr = wfGetDB( DB_REPLICA );
+ $revision = Revision::newKnownCurrent( $dbr, $this->getId(), $latest );
}
- $revision = Revision::newFromPageId( $this->getId(), $latest, $flags );
+
if ( $revision ) { // sanity
$this->setLastEdit( $revision );
}
/**
* Perform the actions of a page purging
+ * @param integer $flags Bitfield of WikiPage::PURGE_* constants
* @return bool
*/
- public function doPurge() {
+ public function doPurge( $flags = self::PURGE_ALL ) {
if ( !Hooks::run( 'ArticlePurge', [ &$this ] ) ) {
return false;
}
- $this->mTitle->invalidateCache();
- // Send purge after above page_touched update was committed
- DeferredUpdates::addUpdate(
- new CdnCacheUpdate( $this->mTitle->getCdnUrls() ),
- DeferredUpdates::PRESEND
- );
+ if ( ( $flags & self::PURGE_GLOBAL_PCACHE ) == self::PURGE_GLOBAL_PCACHE ) {
+ // Set page_touched in the database to invalidate all DC caches
+ $this->mTitle->invalidateCache();
+ } elseif ( ( $flags & self::PURGE_CLUSTER_PCACHE ) == self::PURGE_CLUSTER_PCACHE ) {
+ // Delete the parser options key in the local cluster to invalidate the DC cache
+ ParserCache::singleton()->deleteOptionsKey( $this );
+ // Avoid sending HTTP 304s in ViewAction to the client who just issued the purge
+ $cache = ObjectCache::getLocalClusterInstance();
+ $cache->set(
+ $cache->makeKey( 'page', 'last-dc-purge', $this->getId() ),
+ wfTimestamp( TS_MW ),
+ $cache::TTL_HOUR
+ );
+ }
+
+ if ( ( $flags & self::PURGE_CDN_CACHE ) == self::PURGE_CDN_CACHE ) {
+ // Clear any HTML file cache
+ HTMLFileCache::clearFileCache( $this->getTitle() );
+ // Send purge after any page_touched above update was committed
+ DeferredUpdates::addUpdate(
+ new CdnCacheUpdate( $this->mTitle->getCdnUrls() ),
+ DeferredUpdates::PRESEND
+ );
+ }
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
// @todo move this logic to MessageCache
return true;
}
+ /**
+ * Get the last time a user explicitly purged the page via action=purge
+ *
+ * @return string|bool TS_MW timestamp or false
+ * @since 1.28
+ */
+ public function getLastPurgeTimestamp() {
+ $cache = ObjectCache::getLocalClusterInstance();
+
+ return $cache->get( $cache->makeKey( 'page', 'last-dc-purge', $this->getId() ) );
+ }
+
/**
* Insert a new empty page record for this article.
* This *must* be followed up by creating a revision
*/
public function doEditContent(
Content $content, $summary, $flags = 0, $baseRevId = false,
- User $user = null, $serialFormat = null, $tags = null
+ User $user = null, $serialFormat = null, $tags = []
) {
global $wgUser, $wgUseAutomaticEditSummaries;
+ // Old default parameter for $tags was null
+ if ( $tags === null ) {
+ $tags = [];
+ }
+
// Low-level sanity check
if ( $this->mTitle->getText() === '' ) {
throw new MWException( 'Something is trying to edit an article with an empty title' );
$old_revision = $this->getRevision(); // current revision
$old_content = $this->getContent( Revision::RAW ); // current revision's content
+ if ( $old_content && $old_content->getModel() !== $content->getModel() ) {
+ $tags[] = 'mw-contentmodelchange';
+ }
+
// Provide autosummaries if one is not provided and autosummaries are enabled
if ( $wgUseAutomaticEditSummaries && ( $flags & EDIT_AUTOSUMMARY ) && $summary == '' ) {
$handler = $content->getContentHandler();
Hooks::run( 'WikiPageDeletionUpdates', [ $this, $content, &$updates ] );
return $updates;
}
+
+ /**
+ * Whether this content displayed on this page
+ * comes from the local database
+ *
+ * @since 1.28
+ * @return bool
+ */
+ public function isLocal() {
+ return true;
+ }
}
public $mDefaultDirection = IndexPager::DIR_DESCENDING;
public $mYear;
public $mMonth;
+ public $mDay;
function getNavigationBar() {
if ( !$this->isNavigationBarShown() ) {
return $this->mNavigationBar;
}
- function getDateCond( $year, $month ) {
+ /**
+ * Set and return the mOffset timestamp such that we can get all revisions with
+ * a timestamp up to the specified parameters.
+ * @param int $year Year up to which we want revisions
+ * @param int $month Month up to which we want revisions
+ * @param int $day [optional] Day up to which we want revisions. Default is end of month.
+ * @return string|null Timestamp or null if year and month are false/invalid
+ */
+ function getDateCond( $year, $month, $day = -1 ) {
$year = intval( $year );
$month = intval( $month );
+ $day = intval( $day );
- // Basic validity checks
+ // Basic validity checks for year and month
$this->mYear = $year > 0 ? $year : false;
$this->mMonth = ( $month > 0 && $month < 13 ) ? $month : false;
- // Given an optional year and month, we need to generate a timestamp
- // to use as "WHERE rev_timestamp <= result"
- // Examples: year = 2006 equals < 20070101 (+000000)
- // year=2005, month=1 equals < 20050201
- // year=2005, month=12 equals < 20060101
+ // If year and month are false, don't update the mOffset
if ( !$this->mYear && !$this->mMonth ) {
return;
}
+ // Given an optional year, month, and day, we need to generate a timestamp
+ // to use as "WHERE rev_timestamp <= result"
+ // Examples: year = 2006 equals < 20070101 (+000000)
+ // year=2005, month=1 equals < 20050201
+ // year=2005, month=12 equals < 20060101
+ // year=2005, month=12, day=5 equals < 20051206
if ( $this->mYear ) {
$year = $this->mYear;
} else {
}
if ( $this->mMonth ) {
- $month = $this->mMonth + 1;
- // For December, we want January 1 of the next year
+ $month = $this->mMonth;
+
+ // Day validity check after we have month and year checked
+ $this->mDay = checkdate( $month, $day, $year ) ? $day : false;
+
+ if ( $this->mDay ) {
+ // If we have a day, we want up to the day immediately afterward
+ $day = $this->mDay + 1;
+
+ // Did we overflow the current month?
+ if ( !checkdate( $month, $day, $year ) ) {
+ $day = 1;
+ $month++;
+ }
+ } else {
+ // If no day, assume beginning of next month
+ $day = 1;
+ $month++;
+ }
+
+ // Did we overflow the current year?
if ( $month > 12 ) {
$month = 1;
$year++;
}
+
} else {
// No month implies we want up to the end of the year in question
$month = 1;
+ $day = 1;
$year++;
}
$year = 2032;
}
- $ymd = (int)sprintf( "%04d%02d01", $year, $month );
+ $ymd = (int)sprintf( "%04d%02d%02d", $year, $month, $day );
if ( $ymd > 20320101 ) {
$ymd = 20320101;
$timestamp->setTimezone( $this->getConfig()->get( 'Localtimezone' ) );
$this->mOffset = $this->mDb->timestamp( $timestamp->getTimestamp() );
+ return $this->mOffset;
}
}
} elseif ( isset( $m[5] ) && $m[5] !== '' ) {
# RFC or PMID
if ( substr( $m[0], 0, 3 ) === 'RFC' ) {
+ if ( !$this->mOptions->getMagicRFCLinks() ) {
+ return $m[0];
+ }
$keyword = 'RFC';
$urlmsg = 'rfcurl';
$cssClass = 'mw-magiclink-rfc';
$id = $m[5];
} elseif ( substr( $m[0], 0, 4 ) === 'PMID' ) {
+ if ( !$this->mOptions->getMagicPMIDLinks() ) {
+ return $m[0];
+ }
$keyword = 'PMID';
$urlmsg = 'pubmedurl';
$cssClass = 'mw-magiclink-pmid';
}
$url = wfMessage( $urlmsg, $id )->inContentLanguage()->text();
return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $cssClass, [], $this->mTitle );
- } elseif ( isset( $m[6] ) && $m[6] !== '' ) {
+ } elseif ( isset( $m[6] ) && $m[6] !== ''
+ && $this->mOptions->getMagicISBNLinks()
+ ) {
# ISBN
$isbn = $m[6];
$space = self::SPACE_NOT_NL; # non-newline space
* Replace external links (REL)
*
* Note: this is all very hackish and the order of execution matters a lot.
- * Make sure to run tests/parserTests.php if you change this code.
+ * Make sure to run tests/parser/parserTests.php if you change this code.
*
* @private
*
}
/**
- * @param WikiPage $article
+ * @param WikiPage $page
* @return mixed|string
*/
- protected function getOptionsKey( $article ) {
- $pageid = $article->getId();
- return wfMemcKey( 'pcache', 'idoptions', "{$pageid}" );
+ protected function getOptionsKey( $page ) {
+ return wfMemcKey( 'pcache', 'idoptions', $page->getId() );
+ }
+
+ /**
+ * @param WikiPage $page
+ * @since 1.28
+ */
+ public function deleteOptionsKey( $page ) {
+ $this->mMemc->delete( $this->getOptionsKey( $page ) );
}
/**
*/
private $mExtraKey = '';
+ /**
+ * Are magic ISBN links enabled?
+ */
+ private $mMagicISBNLinks = true;
+
+ /**
+ * Are magic PMID links enabled?
+ */
+ private $mMagicPMIDLinks = true;
+
+ /**
+ * Are magic RFC links enabled?
+ */
+ private $mMagicRFCLinks = true;
+
/**
* Function to be called when an option is accessed.
*/
return $this->getUserLangObj()->getCode();
}
+ /**
+ * @since 1.28
+ * @return bool
+ */
+ public function getMagicISBNLinks() {
+ return $this->mMagicISBNLinks;
+ }
+
+ /**
+ * @since 1.28
+ * @return bool
+ */
+ public function getMagicPMIDLinks() {
+ return $this->mMagicPMIDLinks;
+ }
+ /**
+ * @since 1.28
+ * @return bool
+ */
+ public function getMagicRFCLinks() {
+ return $this->mMagicRFCLinks;
+ }
public function setInterwikiMagic( $x ) {
return wfSetVar( $this->mInterwikiMagic, $x );
}
$wgAllowExternalImagesFrom, $wgEnableImageWhitelist, $wgAllowSpecialInclusion,
$wgMaxArticleSize, $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth,
$wgCleanSignatures, $wgExternalLinkTarget, $wgExpensiveParserFunctionLimit,
- $wgMaxGeneratedPPNodeCount, $wgDisableLangConversion, $wgDisableTitleConversion;
+ $wgMaxGeneratedPPNodeCount, $wgDisableLangConversion, $wgDisableTitleConversion,
+ $wgEnableMagicLinks;
// *UPDATE* ParserOptions::matches() if any of this changes as needed
$this->mInterwikiMagic = $wgInterwikiMagic;
$this->mExternalLinkTarget = $wgExternalLinkTarget;
$this->mDisableContentConversion = $wgDisableLangConversion;
$this->mDisableTitleConversion = $wgDisableLangConversion || $wgDisableTitleConversion;
+ $this->mMagicISBNLinks = $wgEnableMagicLinks['ISBN'];
+ $this->mMagicPMIDLinks = $wgEnableMagicLinks['PMID'];
+ $this->mMagicRFCLinks = $wgEnableMagicLinks['RFC'];
$this->mUser = $user;
$this->mNumberHeadings = $user->getOption( 'numberheadings' );
+++ /dev/null
-<?php
-/**
- * Transaction profiling for contention
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Profiler
- * @author Aaron Schulz
- */
-
-use Psr\Log\LoggerInterface;
-use Psr\Log\LoggerAwareInterface;
-use Psr\Log\NullLogger;
-
-/**
- * Helper class that detects high-contention DB queries via profiling calls
- *
- * This class is meant to work with a DatabaseBase object, which manages queries
- *
- * @since 1.24
- */
-class TransactionProfiler implements LoggerAwareInterface {
- /** @var float Seconds */
- protected $dbLockThreshold = 3.0;
- /** @var float Seconds */
- protected $eventThreshold = .25;
- /** @var bool */
- protected $silenced = false;
-
- /** @var array transaction ID => (write start time, list of DBs involved) */
- protected $dbTrxHoldingLocks = [];
- /** @var array transaction ID => list of (query name, start time, end time) */
- protected $dbTrxMethodTimes = [];
-
- /** @var array */
- protected $hits = [
- 'writes' => 0,
- 'queries' => 0,
- 'conns' => 0,
- 'masterConns' => 0
- ];
- /** @var array */
- protected $expect = [
- 'writes' => INF,
- 'queries' => INF,
- 'conns' => INF,
- 'masterConns' => INF,
- 'maxAffected' => INF,
- 'readQueryTime' => INF,
- 'writeQueryTime' => INF
- ];
- /** @var array */
- protected $expectBy = [];
-
- /**
- * @var LoggerInterface
- */
- private $logger;
-
- public function __construct() {
- $this->setLogger( new NullLogger() );
- }
-
- public function setLogger( LoggerInterface $logger ) {
- $this->logger = $logger;
- }
-
- /**
- * @param bool $value
- * @since 1.28
- */
- public function setSilenced( $value ) {
- $this->silenced = $value;
- }
-
- /**
- * Set performance expectations
- *
- * With conflicting expectations, the most narrow ones will be used
- *
- * @param string $event (writes,queries,conns,mConns)
- * @param integer $value Maximum count of the event
- * @param string $fname Caller
- * @since 1.25
- */
- public function setExpectation( $event, $value, $fname ) {
- $this->expect[$event] = isset( $this->expect[$event] )
- ? min( $this->expect[$event], $value )
- : $value;
- if ( $this->expect[$event] == $value ) {
- $this->expectBy[$event] = $fname;
- }
- }
-
- /**
- * Set multiple performance expectations
- *
- * With conflicting expectations, the most narrow ones will be used
- *
- * @param array $expects Map of (event => limit)
- * @param $fname
- * @since 1.26
- */
- public function setExpectations( array $expects, $fname ) {
- foreach ( $expects as $event => $value ) {
- $this->setExpectation( $event, $value, $fname );
- }
- }
-
- /**
- * Reset performance expectations and hit counters
- *
- * @since 1.25
- */
- public function resetExpectations() {
- foreach ( $this->hits as &$val ) {
- $val = 0;
- }
- unset( $val );
- foreach ( $this->expect as &$val ) {
- $val = INF;
- }
- unset( $val );
- $this->expectBy = [];
- }
-
- /**
- * Mark a DB as having been connected to with a new handle
- *
- * Note that there can be multiple connections to a single DB.
- *
- * @param string $server DB server
- * @param string $db DB name
- * @param bool $isMaster
- */
- public function recordConnection( $server, $db, $isMaster ) {
- // Report when too many connections happen...
- if ( $this->hits['conns']++ == $this->expect['conns'] ) {
- $this->reportExpectationViolated( 'conns', "[connect to $server ($db)]" );
- }
- if ( $isMaster && $this->hits['masterConns']++ == $this->expect['masterConns'] ) {
- $this->reportExpectationViolated( 'masterConns', "[connect to $server ($db)]" );
- }
- }
-
- /**
- * Mark a DB as in a transaction with one or more writes pending
- *
- * Note that there can be multiple connections to a single DB.
- *
- * @param string $server DB server
- * @param string $db DB name
- * @param string $id ID string of transaction
- */
- public function transactionWritingIn( $server, $db, $id ) {
- $name = "{$server} ({$db}) (TRX#$id)";
- if ( isset( $this->dbTrxHoldingLocks[$name] ) ) {
- $this->logger->info( "Nested transaction for '$name' - out of sync." );
- }
- $this->dbTrxHoldingLocks[$name] = [
- 'start' => microtime( true ),
- 'conns' => [], // all connections involved
- ];
- $this->dbTrxMethodTimes[$name] = [];
-
- foreach ( $this->dbTrxHoldingLocks as $name => &$info ) {
- // Track all DBs in transactions for this transaction
- $info['conns'][$name] = 1;
- }
- }
-
- /**
- * Register the name and time of a method for slow DB trx detection
- *
- * This assumes that all queries are synchronous (non-overlapping)
- *
- * @param string $query Function name or generalized SQL
- * @param float $sTime Starting UNIX wall time
- * @param bool $isWrite Whether this is a write query
- * @param integer $n Number of affected rows
- */
- public function recordQueryCompletion( $query, $sTime, $isWrite = false, $n = 0 ) {
- $eTime = microtime( true );
- $elapsed = ( $eTime - $sTime );
-
- if ( $isWrite && $n > $this->expect['maxAffected'] ) {
- $this->logger->info( "Query affected $n row(s):\n" . $query . "\n" .
- wfBacktrace( true ) );
- }
-
- // Report when too many writes/queries happen...
- if ( $this->hits['queries']++ == $this->expect['queries'] ) {
- $this->reportExpectationViolated( 'queries', $query );
- }
- if ( $isWrite && $this->hits['writes']++ == $this->expect['writes'] ) {
- $this->reportExpectationViolated( 'writes', $query );
- }
- // Report slow queries...
- if ( !$isWrite && $elapsed > $this->expect['readQueryTime'] ) {
- $this->reportExpectationViolated( 'readQueryTime', $query, $elapsed );
- }
- if ( $isWrite && $elapsed > $this->expect['writeQueryTime'] ) {
- $this->reportExpectationViolated( 'writeQueryTime', $query, $elapsed );
- }
-
- if ( !$this->dbTrxHoldingLocks ) {
- // Short-circuit
- return;
- } elseif ( !$isWrite && $elapsed < $this->eventThreshold ) {
- // Not an important query nor slow enough
- return;
- }
-
- foreach ( $this->dbTrxHoldingLocks as $name => $info ) {
- $lastQuery = end( $this->dbTrxMethodTimes[$name] );
- if ( $lastQuery ) {
- // Additional query in the trx...
- $lastEnd = $lastQuery[2];
- if ( $sTime >= $lastEnd ) { // sanity check
- if ( ( $sTime - $lastEnd ) > $this->eventThreshold ) {
- // Add an entry representing the time spent doing non-queries
- $this->dbTrxMethodTimes[$name][] = [ '...delay...', $lastEnd, $sTime ];
- }
- $this->dbTrxMethodTimes[$name][] = [ $query, $sTime, $eTime ];
- }
- } else {
- // First query in the trx...
- if ( $sTime >= $info['start'] ) { // sanity check
- $this->dbTrxMethodTimes[$name][] = [ $query, $sTime, $eTime ];
- }
- }
- }
- }
-
- /**
- * Mark a DB as no longer in a transaction
- *
- * This will check if locks are possibly held for longer than
- * needed and log any affected transactions to a special DB log.
- * Note that there can be multiple connections to a single DB.
- *
- * @param string $server DB server
- * @param string $db DB name
- * @param string $id ID string of transaction
- * @param float $writeTime Time spent in write queries
- */
- public function transactionWritingOut( $server, $db, $id, $writeTime = 0.0 ) {
- $name = "{$server} ({$db}) (TRX#$id)";
- if ( !isset( $this->dbTrxMethodTimes[$name] ) ) {
- $this->logger->info( "Detected no transaction for '$name' - out of sync." );
- return;
- }
-
- $slow = false;
-
- // Warn if too much time was spend writing...
- if ( $writeTime > $this->expect['writeQueryTime'] ) {
- $this->reportExpectationViolated(
- 'writeQueryTime',
- "[transaction $id writes to {$server} ({$db})]",
- $writeTime
- );
- $slow = true;
- }
- // Fill in the last non-query period...
- $lastQuery = end( $this->dbTrxMethodTimes[$name] );
- if ( $lastQuery ) {
- $now = microtime( true );
- $lastEnd = $lastQuery[2];
- if ( ( $now - $lastEnd ) > $this->eventThreshold ) {
- $this->dbTrxMethodTimes[$name][] = [ '...delay...', $lastEnd, $now ];
- }
- }
- // Check for any slow queries or non-query periods...
- foreach ( $this->dbTrxMethodTimes[$name] as $info ) {
- $elapsed = ( $info[2] - $info[1] );
- if ( $elapsed >= $this->dbLockThreshold ) {
- $slow = true;
- break;
- }
- }
- if ( $slow ) {
- $dbs = implode( ', ', array_keys( $this->dbTrxHoldingLocks[$name]['conns'] ) );
- $msg = "Sub-optimal transaction on DB(s) [{$dbs}]:\n";
- foreach ( $this->dbTrxMethodTimes[$name] as $i => $info ) {
- list( $query, $sTime, $end ) = $info;
- $msg .= sprintf( "%d\t%.6f\t%s\n", $i, ( $end - $sTime ), $query );
- }
- $this->logger->info( $msg );
- }
- unset( $this->dbTrxHoldingLocks[$name] );
- unset( $this->dbTrxMethodTimes[$name] );
- }
-
- /**
- * @param string $expect
- * @param string $query
- * @param string|float|int $actual [optional]
- */
- protected function reportExpectationViolated( $expect, $query, $actual = null ) {
- if ( $this->silenced ) {
- return;
- }
-
- $n = $this->expect[$expect];
- $by = $this->expectBy[$expect];
- $actual = ( $actual !== null ) ? " (actual: $actual)" : "";
-
- $this->logger->info(
- "Expectation ($expect <= $n) by $by not met$actual:\n$query\n" .
- wfBacktrace( true )
- );
- }
-}
* Values considered empty:
*
* - null
- * - array()
+ * - []
* - new XmlJsCode( '{}' )
- * - new stdClass() // (object) array()
+ * - new stdClass() // (object) []
*
* @param Array $array
*/
* // Scripts to always include
* 'scripts' => [file path string or array of file path strings],
* // Scripts to include in specific language contexts
- * 'languageScripts' => array(
+ * 'languageScripts' => [
* [language code] => [file path string or array of file path strings],
- * ),
+ * ],
* // Scripts to include in specific skin contexts
- * 'skinScripts' => array(
+ * 'skinScripts' => [
* [skin name] => [file path string or array of file path strings],
- * ),
+ * ],
* // Scripts to include in debug contexts
* 'debugScripts' => [file path string or array of file path strings],
* // Modules which must be loaded before this module
* 'dependencies' => [module name string or array of module name strings],
- * 'templates' => array(
+ * 'templates' => [
* [template alias with file.ext] => [file path to a template file],
- * ),
+ * ],
* // Styles to always load
* 'styles' => [file path string or array of file path strings],
* // Styles to include in specific skin contexts
- * 'skinStyles' => array(
+ * 'skinStyles' => [
* [skin name] => [file path string or array of file path strings],
- * ),
+ * ],
* // Messages to always load
* 'messages' => [array of message key strings],
* // Group which this module should be loaded together with
* Below is a description for the $options array:
* @par Construction options:
* @code
- * array(
+ * [
* // Base path to prepend to all local paths in $options. Defaults to $IP
* 'localBasePath' => [base path],
* // Path to JSON file that contains any of the settings below
* 'selectorWithoutVariant' => [CSS selector template, variables: {prefix} {name}],
* 'selectorWithVariant' => [CSS selector template, variables: {prefix} {name} {variant}],
* // List of variants that may be used for the image files
- * 'variants' => array(
- * [theme name] => array(
- * [variant name] => array(
+ * 'variants' => [
+ * [theme name] => [
+ * [variant name] => [
* 'color' => [color string, e.g. '#ffff00'],
* 'global' => [boolean, if true, this variant is available
* for all images of this type],
- * ),
+ * ],
* ...
- * ),
+ * ],
* ...
- * ),
+ * ],
* // List of image files and their options
- * 'images' => array(
- * [theme name] => array(
- * [icon name] => array(
+ * 'images' => [
+ * [theme name] => [
+ * [icon name] => [
* 'file' => [file path string or array whose values are file path strings
* and whose keys are 'default', 'ltr', 'rtl', a single
* language code like 'en', or a list of language codes like
* 'en,de,ar'],
* 'variants' => [array of variant name strings, variants
* available for this image],
- * ),
+ * ],
* ...
- * ),
+ * ],
* ...
- * ),
- * )
+ * ],
+ * ]
* @endcode
* @throws InvalidArgumentException
*/
]
);
- $dbw->onTransactionResolution( function () use ( &$scopeLock ) {
- ScopedCallback::consume( $scopeLock ); // release after commit
- } );
+ if ( $dbw->trxLevel() ) {
+ $dbw->onTransactionResolution( function () use ( &$scopeLock ) {
+ ScopedCallback::consume( $scopeLock ); // release after commit
+ } );
+ }
}
} catch ( Exception $e ) {
wfDebugLog( 'resourceloader', __METHOD__ . ": failed to update DB: $e" );
*
* @code
* $summary = parent::getDefinitionSummary( $context );
- * $summary[] = array(
+ * $summary[] = [
* 'foo' => 123,
* 'bar' => 'quux',
- * );
+ * ];
* return $summary;
* @endcode
*
sort( $pageNames );
$key = implode( '|', $pageNames );
if ( !isset( $this->titleInfo[$key] ) ) {
- $this->titleInfo[$key] = self::fetchTitleInfo( $dbr, $pageNames, __METHOD__ );
+ $this->titleInfo[$key] = static::fetchTitleInfo( $dbr, $pageNames, __METHOD__ );
}
return $this->titleInfo[$key];
}
- private static function fetchTitleInfo( IDatabase $db, array $pages, $fname = __METHOD__ ) {
+ protected static function fetchTitleInfo( IDatabase $db, array $pages, $fname = __METHOD__ ) {
$titleInfo = [];
$batch = new LinkBatch;
foreach ( $pages as $titleText ) {
}
}
}
- $allInfo = self::fetchTitleInfo( $db, array_keys( $allPages ), __METHOD__ );
+ $allInfo = static::fetchTitleInfo( $db, array_keys( $allPages ), __METHOD__ );
foreach ( $wikiModules as $module ) {
$pages = $module->getPages( $context );
- $info = array_intersect_key( $allInfo, $pages );
+ // Before we intersect, map the names to canonical form (T145673).
+ $intersect = [];
+ foreach ( $pages as $page => $unused ) {
+ $title = Title::newFromText( $page )->getPrefixedText();
+ $intersect[$title] = 1;
+ }
+ $info = array_intersect_key( $allInfo, $intersect );
+
$pageNames = array_keys( $pages );
sort( $pageNames );
$key = implode( '|', $pageNames );
public function doQuery( $db ) {
$ids = array_map( 'intval', $this->ids );
$queryInfo = [
- 'tables' => [ 'revision', 'user' ],
+ 'tables' => [ 'revision', 'page', 'user' ],
'fields' => array_merge( Revision::selectFields(), Revision::selectUserFields() ),
'conds' => [
'rev_page' => $this->title->getArticleID(),
'rev_id' => $ids,
],
- 'options' => [ 'ORDER BY' => 'rev_id DESC' ],
+ 'options' => [
+ 'ORDER BY' => 'rev_id DESC',
+ 'USE INDEX' => [ 'revision' => 'PRIMARY' ] // workaround for MySQL bug (T104313)
+ ],
'join_conds' => [
'page' => Revision::pageJoinCond(),
'user' => Revision::userJoinCond(),
/**
* Get the string representation of the token at a timestamp
- * @param int timestamp
+ * @param int $timestamp
* @return string
*/
protected function toStringAtTimestamp( $timestamp ) {
*
* If a "data" key is present, it must be an array, where the keys represent
* the data-xxx properties with their provided values. For example,
- * $item['data'] = array(
+ * $item['data'] = [
* 'foo' => 1,
* 'bar' => 'baz',
- * );
+ * ];
* will render as element properties:
* data-foo='1' data-bar='baz'
*
* a link in. This should be an array of arrays containing a 'tag' and
* optionally an 'attributes' key. If you only have one element you don't
* need to wrap it in another array. eg: To use <a><span>...</span></a>
- * in all links use array( 'text-wrapper' => array( 'tag' => 'span' ) )
+ * in all links use [ 'text-wrapper' => [ 'tag' => 'span' ] ]
* for your options.
* - 'link-class' key can be used to specify additional classes to apply
* to all links.
*
* BaseTemplate::getSidebar can be used to simplify the format and id generation in new skins.
*
- * The format of the returned array is array( heading => content, ... ), where:
+ * The format of the returned array is [ heading => content, ... ], where:
* - heading is the heading of a navigation portlet. It is either:
* - magic string to be handled by the skins ('SEARCH' / 'LANGUAGES' / 'TOOLBOX' / ...)
* - a message name (e.g. 'navigation'), the message should be HTML-escaped by the skin
*
* @file
*/
+
+use MediaWiki\Auth\AuthManager;
use MediaWiki\MediaWikiServices;
/**
$title = $this->getTitle();
$request = $this->getRequest();
$pageurl = $title->getLocalURL();
+ $authManager = AuthManager::singleton();
/* set up the default links for the personal toolbar */
$personal_urls = [];
'href' => $href,
'active' => $active
];
- $personal_urls['logout'] = [
- 'text' => $this->msg( 'pt-userlogout' )->text(),
- 'href' => self::makeSpecialUrl( 'Userlogout',
- // userlogout link must always contain an & character, otherwise we might not be able
- // to detect a buggy precaching proxy (bug 17790)
- $title->isSpecial( 'Preferences' ) ? 'noreturnto' : $returnto
- ),
- 'active' => false
- ];
+
+ // if we can't set the user, we can't unset it either
+ if ( $request->getSession()->canSetUser() ) {
+ $personal_urls['logout'] = [
+ 'text' => $this->msg( 'pt-userlogout' )->text(),
+ 'href' => self::makeSpecialUrl( 'Userlogout',
+ // userlogout link must always contain an & character, otherwise we might not be able
+ // to detect a buggy precaching proxy (bug 17790)
+ $title->isSpecial( 'Preferences' ) ? 'noreturnto' : $returnto ),
+ 'active' => false
+ ];
+ }
} else {
$useCombinedLoginLink = $this->useCombinedLoginLink();
+ if ( !$authManager->canCreateAccounts() || !$authManager->canAuthenticateNow() ) {
+ // don't show combined login/signup link if one of those is actually not available
+ $useCombinedLoginLink = false;
+ }
+
$loginlink = $this->getUser()->isAllowed( 'createaccount' ) && $useCombinedLoginLink
? 'nav-login-createaccount'
: 'pt-login';
];
}
- if ( $this->getUser()->isAllowed( 'createaccount' ) && !$useCombinedLoginLink ) {
+ if (
+ $authManager->canCreateAccounts()
+ && $this->getUser()->isAllowed( 'createaccount' )
+ && !$useCombinedLoginLink
+ ) {
$personal_urls['createaccount'] = $createaccount_url;
}
- $personal_urls['login'] = $login_url;
+ if ( $authManager->canAuthenticateNow() ) {
+ $personal_urls['login'] = $login_url;
+ }
}
Hooks::run( 'PersonalUrls', [ &$personal_urls, &$title, $this ] );
$content_navigation['namespaces'][$talkId]['context'] = 'talk';
if ( $userCanRead ) {
- $isForeignFile = $title->inNamespace( NS_FILE ) && $this->canUseWikiPage() &&
- $this->getWikiPage() instanceof WikiFilePage && !$this->getWikiPage()->isLocal();
-
- // Adds view view link
- if ( $title->exists() || $isForeignFile ) {
+ // Adds "view" view link
+ if ( $title->isKnown() ) {
$content_navigation['views']['view'] = $this->tabAction(
$isTalk ? $talkPage : $subjectPage,
[ "$skname-view-view", 'view' ],
$content_navigation['views']['view']['redundant'] = true;
}
+ $isForeignFile = $title->inNamespace( NS_FILE ) && $this->canUseWikiPage() &&
+ $this->getWikiPage() instanceof WikiFilePage && !$this->getWikiPage()->isLocal();
+
// If it is a non-local file, show a link to the file in its own repository
+ // @todo abstract this for remote content that isn't a file
if ( $isForeignFile ) {
$file = $this->getWikiPage()->getFile();
$content_navigation['views']['view-foreign'] = [
'href' => $title->getLocalURL( 'action=edit§ion=new' )
];
}
- // Checks if the page has some kind of viewable content
+ // Checks if the page has some kind of viewable source content
} elseif ( $title->hasSourceText() ) {
// Adds view source view link
$content_navigation['views']['viewsource'] = [
$this->setHeaders();
$this->checkPermissions();
- // Make sure it's possible to log in
- if ( !$this->isSignup() && !$session->canSetUser() ) {
- throw new ErrorPageError( 'cannotloginnow-title', 'cannotloginnow-text', [
+ // Make sure the system configuration allows log in / sign up
+ if ( !$this->isSignup() && !$authManager->canAuthenticateNow() ) {
+ if ( !$session->canSetUser() ) {
+ throw new ErrorPageError( 'cannotloginnow-title', 'cannotloginnow-text', [
$session->getProvider()->describe( RequestContext::getMain()->getLanguage() )
] );
+ }
+ throw new ErrorPageError( 'cannotlogin-title', 'cannotlogin-text' );
+ } elseif ( $this->isSignup() && !$authManager->canCreateAccounts() ) {
+ throw new ErrorPageError( 'cannotcreateaccount-title', 'cannotcreateaccount-text' );
}
/*
] );
if ( $this->operation === 'insert' || !empty( $data['resetPassword'] ) ) {
- $this->password = PasswordFactory::generateRandomPasswordString(
- max( 32, $this->getConfig()->get( 'MinimalPasswordLength' ) )
- );
+ $this->password = BotPassword::generatePassword( $this->getConfig() );
$passwordFactory = new PasswordFactory();
$passwordFactory->init( RequestContext::getMain()->getConfig() );
$password = $passwordFactory->newFromPlaintext( $this->password );
$out->addWikiMsg(
'botpasswords-newpassword',
htmlspecialchars( $username . $sep . $this->par ),
- htmlspecialchars( $this->password )
+ htmlspecialchars( $this->password ),
+ htmlspecialchars( $username ),
+ htmlspecialchars( $this->par . $sep . $this->password )
);
$this->password = null;
}
# Truncate for whole multibyte characters.
$reason = $wgContLang->truncate( $reason, 255 );
+ // Run edit filters
+ $derivativeContext = new DerivativeContext( $this->getContext() );
+ $derivativeContext->setTitle( $this->title );
+ $derivativeContext->setWikiPage( $page );
+ $status = new Status();
+ if ( !Hooks::run( 'EditFilterMergedContent',
+ [ $derivativeContext, $newContent, $status, $reason,
+ $user, false ] )
+ ) {
+ if ( $status->isGood() ) {
+ // TODO: extensions should really specify an error message
+ $status->fatal( 'hookaborted' );
+ }
+ return $status;
+ }
+
$status = $page->doEditContent(
$newContent,
$reason,
protected $explicitlyDefinedTags;
/**
- * @var array List of extension defined tags
+ * @var array List of software defined tags
*/
- protected $extensionDefinedTags;
+ protected $softwareDefinedTags;
/**
- * @var array List of extension activated tags
+ * @var array List of software activated tags
*/
- protected $extensionActivatedTags;
+ protected $softwareActivatedTags;
function __construct() {
parent::__construct( 'Tags' );
// Used in #doTagRow()
$this->explicitlyDefinedTags = array_fill_keys(
ChangeTags::listExplicitlyDefinedTags(), true );
- $this->extensionDefinedTags = array_fill_keys(
- ChangeTags::listExtensionDefinedTags(), true );
+ $this->softwareDefinedTags = array_fill_keys(
+ ChangeTags::listSoftwareDefinedTags(), true );
// List all defined tags, even if they were never applied
- $definedTags = array_keys( $this->explicitlyDefinedTags + $this->extensionDefinedTags );
+ $definedTags = array_keys( $this->explicitlyDefinedTags + $this->softwareDefinedTags );
// Show header only if there exists atleast one tag
if ( !$tagStats && !$definedTags ) {
);
// Used in #doTagRow()
- $this->extensionActivatedTags = array_fill_keys(
- ChangeTags::listExtensionActivatedTags(), true );
+ $this->softwareActivatedTags = array_fill_keys(
+ ChangeTags::listSoftwareActivatedTags(), true );
// Insert tags that have been applied at least once
foreach ( $tagStats as $tag => $hitcount ) {
$newRow .= Xml::tags( 'td', null, $desc );
$sourceMsgs = [];
- $isExtension = isset( $this->extensionDefinedTags[$tag] );
+ $isSoftware = isset( $this->softwareDefinedTags[$tag] );
$isExplicit = isset( $this->explicitlyDefinedTags[$tag] );
- if ( $isExtension ) {
+ if ( $isSoftware ) {
+ // TODO: Rename this message
$sourceMsgs[] = $this->msg( 'tags-source-extension' )->escaped();
}
if ( $isExplicit ) {
}
$newRow .= Xml::tags( 'td', null, implode( Xml::element( 'br' ), $sourceMsgs ) );
- $isActive = $isExplicit || isset( $this->extensionActivatedTags[$tag] );
+ $isActive = $isExplicit || isset( $this->softwareActivatedTags[$tag] );
$activeMsg = ( $isActive ? 'tags-active-yes' : 'tags-active-no' );
$newRow .= Xml::tags( 'td', null, $this->msg( $activeMsg )->escaped() );
$preText .= $this->msg( 'tags-delete-explanation-warning', $tag )->parseAsBlock();
// see if the tag is in use
- $this->extensionActivatedTags = array_fill_keys(
- ChangeTags::listExtensionActivatedTags(), true );
- if ( isset( $this->extensionActivatedTags[$tag] ) ) {
+ $this->softwareActivatedTags = array_fill_keys(
+ ChangeTags::listSoftwareActivatedTags(), true );
+ if ( isset( $this->softwareActivatedTags[$tag] ) ) {
$preText .= $this->msg( 'tags-delete-explanation-active', $tag )->parseAsBlock();
}
* @ingroup SpecialPage
*/
class UserrightsPage extends SpecialPage {
- # The target of the local right-adjuster's interest. Can be gotten from
- # either a GET parameter or a subpage-style parameter, so have a member
- # variable for it.
+ /**
+ * The target of the local right-adjuster's interest. Can be gotten from
+ * either a GET parameter or a subpage-style parameter, so have a member
+ * variable for it.
+ * @var null|string $mTarget
+ */
protected $mTarget;
/*
* @var null|User $mFetchedUser The user object of the target username or null.
$this->mTarget = $request->getVal( 'user' );
}
+ if ( is_string( $this->mTarget ) ) {
+ $this->mTarget = trim( $this->mTarget );
+ }
+
$available = $this->changeableGroups();
if ( $this->mTarget === null ) {
// Most of this code will use the 'contributions' group DB, which can map to replica DBs
// with extra user based indexes or partioning by user. The additional metadata
// queries should use a regular replica DB since the lookup pattern is not all by user.
- $this->mDbSecondary = wfGetDB( DB_SLAVE ); // any random replica DB
+ $this->mDbSecondary = wfGetDB( DB_REPLICA ); // any random replica DB
$this->mDb = wfGetDB( DB_REPLICA, 'contributions' );
}
// fileprops cache
protected $fileProps = [];
- // current user info
- protected $userId, $isLoggedIn;
+ // current user
+ protected $user, $userId, $isLoggedIn;
/**
* Represents a temporary filestore, with metadata in the database.
* (should replace it eventually).
*
* @param FileRepo $repo
- * @param User $user
+ * @param User $user (default null)
*/
- public function __construct( FileRepo $repo, User $user ) {
+ public function __construct( FileRepo $repo, $user = null ) {
// this might change based on wiki's configuration.
$this->repo = $repo;
- // We only need the logged in status and user id.
- $this->userId = $user->getId();
- $this->isLoggedIn = $user->isLoggedIn();
+ // if a user was passed, use it. otherwise, attempt to use the global.
+ // this keeps FileRepo from breaking when it creates an UploadStash object
+ if ( $user ) {
+ $this->user = $user;
+ } else {
+ global $wgUser;
+ $this->user = $wgUser;
+ }
+
+ if ( is_object( $this->user ) ) {
+ $this->userId = $this->user->getId();
+ $this->isLoggedIn = $this->user->isLoggedIn();
+ }
}
/**
return (bool)$dbw->affectedRows();
}
+ /**
+ * Returns a (raw, unhashed) random password string.
+ * @param Config $config
+ * @return string
+ */
+ public static function generatePassword( $config ) {
+ return PasswordFactory::generateRandomPasswordString(
+ max( 32, $config->get( 'MinimalPasswordLength' ) ) );
+ }
+
+ /**
+ * There are two ways to login with a bot password: "username@appId", "password" and
+ * "username", "appId@password". Transform it so it is always in the first form.
+ * Returns [bot username, bot password, could be normal password?] where the last one is a flag
+ * meaning this could either be a bot password or a normal password, it cannot be decided for
+ * certain (although in such cases it almost always will be a bot password).
+ * If this cannot be a bot password login just return false.
+ * @param string $username
+ * @param string $password
+ * @return array|false
+ */
+ public static function canonicalizeLoginData( $username, $password ) {
+ $sep = BotPassword::getSeparator();
+ // the strlen check helps minimize the password information obtainable from timing
+ if ( strlen( $password ) >= 32 && strpos( $username, $sep ) !== false ) {
+ // the separator is not valid in new usernames but might appear in legacy ones
+ if ( preg_match( '/^[0-9a-w]{32,}$/', $password ) ) {
+ return [ $username, $password, true ];
+ }
+ } elseif ( strlen( $password ) > 32 && strpos( $password, $sep ) !== false ) {
+ $segments = explode( $sep, $password );
+ $password = array_pop( $segments );
+ $appId = implode( $sep, $segments );
+ if ( preg_match( '/^[0-9a-w]{32,}$/', $password ) ) {
+ return [ $username . $sep . $appId, $password, true ];
+ }
+ }
+ return false;
+ }
+
/**
* Try to log the user in
* @param string $username Combined user name and app ID
const AUDIENCE_PUBLIC = 1;
const AUDIENCE_RAW = 2;
- /** @var CentralIdLookup[][] */
+ /** @var CentralIdLookup[] */
private static $instances = [];
/** @var string */
}
$audience = $this->checkAudience( $audience );
- $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
- $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
- ? [ 'LOCK IN SHARE MODE' ]
- : [];
+ list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
+ $db = wfGetDB( $index );
$tables = [ 'user' ];
$fields = [ 'user_id', 'user_name' ];
}
$audience = $this->checkAudience( $audience );
- $db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_REPLICA );
- $options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
- ? [ 'LOCK IN SHARE MODE' ]
- : [];
+ list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
+ $db = wfGetDB( $index );
$tables = [ 'user' ];
$fields = [ 'user_id', 'user_name' ];
* @note If the user doesn't have 'editmywatchlist', this will do nothing.
*/
public function clearAllNotifications() {
- if ( wfReadOnly() ) {
- return;
- }
-
+ global $wgUseEnotif, $wgShowUpdatedMarker;
// Do nothing if not allowed to edit the watchlist
- if ( !$this->isAllowed( 'editmywatchlist' ) ) {
+ if ( wfReadOnly() || !$this->isAllowed( 'editmywatchlist' ) ) {
return;
}
- global $wgUseEnotif, $wgShowUpdatedMarker;
if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
$this->setNewtalk( false );
return;
}
+
$id = $this->getId();
- if ( $id != 0 ) {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->update( 'watchlist',
- [ /* SET */ 'wl_notificationtimestamp' => null ],
- [ /* WHERE */ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
- __METHOD__
- );
- // We also need to clear here the "you have new message" notification for the own user_talk page;
- // it's cleared one page view later in WikiPage::doViewUpdates().
+ if ( !$id ) {
+ return;
}
+
+ $dbw = wfGetDB( DB_MASTER );
+ $asOfTimes = array_unique( $dbw->selectFieldValues(
+ 'watchlist',
+ 'wl_notificationtimestamp',
+ [ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
+ __METHOD__,
+ [ 'ORDER BY' => 'wl_notificationtimestamp DESC', 'LIMIT' => 500 ]
+ ) );
+ if ( !$asOfTimes ) {
+ return;
+ }
+ // Immediately update the most recent touched rows, which hopefully covers what
+ // the user sees on the watchlist page before pressing "mark all pages visited"....
+ $dbw->update(
+ 'watchlist',
+ [ 'wl_notificationtimestamp' => null ],
+ [ 'wl_user' => $id, 'wl_notificationtimestamp' => $asOfTimes ],
+ __METHOD__
+ );
+ // ...and finish the older ones in a post-send update with lag checks...
+ DeferredUpdates::addUpdate( new AutoCommitUpdate(
+ $dbw,
+ __METHOD__,
+ function () use ( $dbw, $id ) {
+ global $wgUpdateRowsPerQuery;
+
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
+ $asOfTimes = array_unique( $dbw->selectFieldValues(
+ 'watchlist',
+ 'wl_notificationtimestamp',
+ [ 'wl_user' => $id, 'wl_notificationtimestamp IS NOT NULL' ],
+ __METHOD__
+ ) );
+ foreach ( array_chunk( $asOfTimes, $wgUpdateRowsPerQuery ) as $asOfTimeBatch ) {
+ $dbw->update(
+ 'watchlist',
+ [ 'wl_notificationtimestamp' => null ],
+ [ 'wl_user' => $id, 'wl_notificationtimestamp' => $asOfTimeBatch ],
+ __METHOD__
+ );
+ $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
+ }
+ }
+ ) );
+ // We also need to clear here the "you have new message" notification for the own
+ // user_talk page; it's cleared one page view later in WikiPage::doViewUpdates().
}
/**
'上系上' => '上繫上',
'上课钟' => '上課鐘',
'上面糊' => '上面糊',
+'下文里' => '下文裡',
'下于' => '下於',
'下梁' => '下樑',
'下注解' => '下注解',
'丑表功' => '丑表功',
'丑角' => '丑角',
'且于' => '且於',
-'世田谷' => '世田谷',
'世界杯' => '世界盃',
'世纪里' => '世紀裡',
'世纪钟' => '世紀鐘',
'干性' => '乾性',
'干打雷' => '乾打雷',
'干折' => '乾折',
+'干拌面' => '乾拌麵',
'干撂台' => '乾撂台',
'干撇下' => '乾撇下',
'干擦' => '乾擦',
'乱发生' => '亂發生',
'乱发脾气' => '亂發脾氣',
'乱发' => '亂髮',
+'乱斗' => '亂鬥',
'乱哄哄' => '亂鬨鬨',
'了然后' => '了然後',
'事有斗巧' => '事有鬥巧',
'刑余' => '刑餘',
'划一桨' => '划一槳',
'划一槳' => '划一槳',
-'划上' => '划上',
-'划下' => '划下',
'划不來' => '划不來',
'划不来' => '划不來',
'划了一会' => '划了一會',
'北回铁路' => '北迴鐵路',
'匪干' => '匪幹',
'匿于' => '匿於',
+'区里有' => '區裡有',
+'区里的' => '區裡的',
'十个' => '十個',
'十出家' => '十出家',
'十出击' => '十出擊',
'卷须' => '卷鬚',
'厂部' => '厂部',
'原子钟' => '原子鐘',
+'原文里' => '原文裡',
'原钟' => '原鐘',
'历物之意' => '厤物之意',
'去山里' => '去山裡',
'口里' => '口裡',
'口钟' => '口鐘',
'古人有云' => '古人有云',
+'古文里' => '古文裡',
'古书云' => '古書云',
'古書云' => '古書云',
'古柯咸' => '古柯鹹',
'寡欲' => '寡慾',
'实干' => '實幹',
'实累累' => '實纍纍',
+'实验里' => '實驗裡',
'写字台' => '寫字檯',
'宽于' => '寬於',
'宽余' => '寬餘',
'干革命' => '幹革命',
'干头' => '幹頭',
'干么' => '幹麼',
+'幽并' => '幽并',
'几个' => '幾個',
'几周后' => '幾周後',
'几天后' => '幾天後',
'怒气冲天' => '怒氣衝天',
'怒火冲天' => '怒火衝天',
'怒发冲冠' => '怒髮衝冠',
+'怜奈' => '怜奈',
'思如泉涌' => '思如泉湧',
'怠于' => '怠於',
'急于' => '急於',
'拉面色' => '拉面色',
'拉面部' => '拉面部',
'拉面' => '拉麵',
+'拌面' => '拌麵',
'拒人于' => '拒人於',
'拒于' => '拒於',
'拓朴' => '拓樸',
'捶炼' => '捶鍊',
'扫荡' => '掃蕩',
'授勋' => '授勳',
+'授时历' => '授時曆',
'掌柜' => '掌柜',
'排干' => '排乾',
'排干部' => '排幹部',
'斫雕为朴' => '斫雕為樸',
'新井里美' => '新井里美',
'新干县' => '新幹縣',
+'新庄子' => '新庄子',
'新历' => '新曆',
'新历史' => '新歷史',
'新扎' => '新紮',
'村落发' => '村落發',
'村里有' => '村裡有',
'村里的' => '村裡的',
+'杜琪峰' => '杜琪峯',
'杜老志道' => '杜老誌道',
'杞宋无征' => '杞宋無徵',
'束发' => '束髮',
'梁上君子' => '梁上君子',
'梁启超' => '梁啓超',
'条干' => '條幹',
+'条文里' => '條文裡',
'梨干' => '梨乾',
'梯冲' => '梯衝',
'械系' => '械繫',
'欧游' => '歐遊',
'止于' => '止於',
'正官庄' => '正官庄',
+'正文里' => '正文裡',
'正杰' => '正杰',
'武丑' => '武丑',
'武后' => '武后',
'水并流' => '水併流',
'水来汤里去' => '水來湯裡去',
'水准' => '水準',
-'水无怜奈' => '水無怜奈',
-'水無怜奈' => '水無怜奈',
'水表示' => '水表示',
'水表面' => '水表面',
'水里' => '水裡',
'泰山梁木' => '泰山梁木',
'泱郁' => '泱鬱',
'泳气钟' => '泳氣鐘',
-'洄游' => '洄遊',
'洋河大曲' => '洋河大麯',
'洒家' => '洒家',
'洒淅' => '洒淅',
'涂敏恒' => '涂敏恆',
'涂泽民' => '涂澤民',
'涂澤民' => '涂澤民',
+'涂尔干' => '涂爾幹',
+'涂爾幹' => '涂爾幹',
'涂紹煃' => '涂紹煃',
'涂绍煃' => '涂紹煃',
'涂羽卿' => '涂羽卿',
'甜面酱' => '甜麵醬',
'生力面' => '生力麵',
'生于' => '生於',
-'生殖洄游' => '生殖洄游',
'生物钟' => '生物鐘',
'生发生' => '生發生',
'生华发' => '生華髮',
'生姜' => '生薑',
'生锈' => '生鏽',
'生发' => '生髮',
-'产卵洄游' => '產卵洄游',
'苏醒' => '甦醒',
'用于' => '用於',
'用法里' => '用法裡',
'甩发' => '甩髮',
'田子里' => '田子里',
'田庄英雄' => '田庄英雄',
-'田谷' => '田穀',
'田里' => '田裡',
'由余' => '由余',
'由于' => '由於',
'毕于' => '畢於',
'毕业于' => '畢業於',
'毕生发展' => '畢生發展',
+'画里' => '畫裡',
'当准' => '當準',
'当当丁丁' => '當當丁丁',
'当当网' => '當當網',
'吁求' => '籲求',
'吁请' => '籲請',
'米沈' => '米瀋',
-'米谷' => '米穀',
'米团' => '米糰',
'米余' => '米餘',
'米面' => '米麵',
'绑扎' => '綁紮',
'绥棱' => '綏稜',
'捆扎' => '綑紮',
+'经文里' => '經文裡',
'經有云' => '經有云',
'经有云' => '經有云',
'综合征' => '綜合徵',
'系上头' => '繫上頭',
'系上黑' => '繫上黑',
'系上,' => '繫上,',
-'系世' => '繫世',
'系到' => '繫到',
'系囚' => '繫囚',
'系心' => '繫心',
'老板' => '老闆',
'老面皮' => '老面皮',
'考征' => '考徵',
+'考试制度' => '考試制度',
'耍斗' => '耍鬥',
'耕获' => '耕穫',
'耳余' => '耳餘',
'苦里' => '苦裡',
'苦斗' => '苦鬥',
'苧麻' => '苧麻',
+'英文里' => '英文裡',
'茂都淀' => '茂都澱',
'范文同' => '范文同',
'范文正公' => '范文正公',
'西历' => '西曆',
'西历史' => '西歷史',
'西湖里' => '西湖里',
-'西米谷' => '西米谷',
'西西里' => '西西里',
'西谷米' => '西谷米',
'西游' => '西遊',
'注生娘娘' => '註生娘娘',
'注疏' => '註疏',
'注脚' => '註腳',
+'注里' => '註裡',
'注解' => '註解',
'注记' => '註記',
'注译' => '註譯',
'谈征' => '談徵',
'请君入瓮' => '請君入甕',
'请托' => '請託',
+'论文里' => '論文裡',
'咨询' => '諮詢',
'诸余' => '諸餘',
'谋干' => '謀幹',
'警报钟' => '警報鐘',
'警示钟' => '警示鐘',
'警钟' => '警鐘',
+'译文里' => '譯文裡',
'译制' => '譯製',
'译注' => '譯註',
'护发' => '護髮',
'额我略历' => '額我略曆',
'额我略历史' => '額我略歷史',
'颜范' => '顏範',
+'颛顼历' => '顓頊曆',
'颠干倒坤' => '顛乾倒坤',
'顛顛仆仆' => '顛顛仆仆',
'颠颠仆仆' => '顛顛仆仆',
'打印度' => '打印度',
'抽烟' => '抽菸',
'抽煙' => '抽菸',
-'拉普兰' => '拉布蘭',
'拒烟' => '拒菸',
'拒煙' => '拒菸',
'卷烟' => '捲菸',
'迈凯轮' => '麥拿輪',
'邁凱輪' => '麥拿輪',
'马萨诸塞' => '麻薩諸塞',
+'粘膜' => '黏膜',
'戴安娜' => '黛安娜',
'狄安娜' => '黛安娜',
'点烟' => '點菸',
'旧金山' => '三藩市',
'舊金山' => '三藩市',
'上台面' => '上枱面',
+'下文里' => '下文裏',
'下著' => '下着',
'下著作' => '下著作',
'下著名' => '下著名',
'動著錄' => '動著錄',
'包著' => '包着',
'北朝鲜' => '北韓',
+'区里有' => '區裏有',
+'区里的' => '區裏的',
'南朝鲜' => '南韓',
'波札那' => '博茨瓦納',
'占卜' => '占卜',
'厄利垂亚' => '厄立特里亞',
'厄利垂亞' => '厄立特里亞',
'源代码' => '原始碼',
+'原文里' => '原文裏',
'去山里' => '去山裏',
'参数里' => '參數裏',
'受著' => '受着',
'受著錄' => '受著錄',
'丛林里' => '叢林裏',
'口里' => '口裏',
+'古文里' => '古文裏',
'只占' => '只佔',
'叫著' => '叫着',
'叫著作' => '叫著作',
'撞球' => '桌球',
'梅鐸' => '梅鐸',
'默多克' => '梅鐸',
+'条文里' => '條文裏',
'梳著' => '梳着',
'梳著作' => '梳著作',
'梳著名' => '梳著名',
'機器人' => '機械人',
'柜台' => '櫃枱',
'柜里' => '櫃裏',
+'正文里' => '正文裏',
'历史里' => '歷史裏',
'死里求生' => '死裏求生',
'死里逃生' => '死裏逃生',
'畫著名' => '畫著名',
'畫著稱' => '畫著稱',
'畫著者' => '畫著者',
+'画里' => '畫裏',
'當著' => '當着',
'當著作' => '當著作',
'過著作' => '當著作',
'綁著者' => '綁著者',
'綁著述' => '綁著述',
'綁著錄' => '綁著錄',
+'经文里' => '經文裏',
'网站里' => '網站裏',
'網路' => '網絡',
'网里' => '網裏',
'苦著錄' => '苦著錄',
'苦里' => '苦裏',
'英占' => '英佔',
+'英文里' => '英文裏',
'共和联邦' => '英聯邦',
'大英國協' => '英聯邦',
'草丛里' => '草叢裏',
'說著者' => '說著者',
'說著述' => '說著述',
'數據機' => '調制解調器',
+'论文里' => '論文裏',
'诺曼底' => '諾曼第',
'警戒著' => '警戒着',
+'譯文里' => '譯文裏',
'變著' => '變着',
'變著作' => '變著作',
'變著名' => '變著名',
"Macofe",
"Carlos Cristia",
"MarcoAurelio",
- "Matma Rex"
+ "Matma Rex",
+ "Fitoschido"
]
},
"tog-underline": "Subrayar os vinclos:",
"yourpassword": "Clau d'acceso:",
"yourpasswordagain": "Torne a escribir a clau:",
"createacct-yourpasswordagain": "Confirma a clau",
- "remembermypassword": "Remerar o mío nombre d'usuario y a clau entre sesions en iste navegador (como muito por $1 {{PLURAL:$1|día|días}})",
"yourdomainname": "Dominio:",
"externaldberror": "Bi habió una error d'autenticación externa d'a base de datos u bien no tiene premisos ta esviellar a suya cuenta externa.",
"login": "Encetar sesión",
"passwordreset-emailtext-user": "L'usuario $1 en {{SITENAME}} ha demandau un recordatorio d'a información d'a suya cuenta en {{SITENAME}} ($4). {{PLURAL:$3|A cuenta d'usuario siguient ye asociata|As cuentas d'usuario siguients son asociatas}} a ista adreza de correu-e:\n\n$2\n\n{{PLURAL:$3|Ista clau d'acceso temporal circumducirá|Istas claus d'acceso temporals circumducirán}} en {{PLURAL:$5|un día|$5 días}}. Habría de connectar-se agora y trigar una nueva clau. Si ista demanda no dimana de vusté, u ya se'n ha acordau d'a suya clau inicial y ya no deseya modificar-la, puet ignorar iste mensache y continar emplegando a suya viella clau.",
"passwordreset-emailelement": "Nombre de usuario: \n$1\n\nClau d'acceso temporal: \n$2",
"passwordreset-emailsentemail": "S'ha ninviau un recordatorio por correu-e.",
- "passwordreset-emailsent-capture": "Se le ha ninviau un recordatorio por correu electronico, que s'amuestra contino.",
- "passwordreset-emailerror-capture": "S'ha chenerau un recordatorio por correu electronico, que s'amuestra contino, pero o ninvío ta l'usuario ha fallau: $1",
"changeemail": "Cambiar l'adreza de correu-e",
"changeemail-header": "Cambiar l'adreza de correu-e d'a cuenta",
"changeemail-no-info": "Debe identificar-se como usuario ta poder acceder dreitament ta ista pachina.",
"minoredit": "He feito una edición menor",
"watchthis": "Cosirar ista pachina",
"savearticle": "Alzar pachina",
+ "publishchanges": "Publicar os cambeos",
"preview": "Previsualización",
"showpreview": "Amostrar previsualización",
"showdiff": "Amostrar cambeos",
"undo-failure": "No se puet desfer a edición pues un atro usuario ha feito una edición intermeya.",
"undo-norev": "No s'ha puesto desfer a edición porque no existiba u ya s'heba borrato.",
"undo-summary": "Desfeita a edición $1 de [[Special:Contributions/$2|$2]] ([[User talk:$2|desc.]])",
- "cantcreateaccounttitle": "No se puede creyar a cuenta",
"cantcreateaccount-text": "A creyación de cuentas dende ixa adreza IP ('''$1''') estió bloqueyata por [[User:$3|$3]].\n\nA razón indicada por $3 ye ''$2''",
"viewpagelogs": "Veyer os rechistros d'ista pachina",
"nohistory": "Ista pachina no tiene un historial d'edicions.",
"special-characters-group-lao": "Laosiano",
"special-characters-group-khmer": "Khmer",
"mw-widgets-dateinput-placeholder-day": "AAAA-MM-DD",
- "mw-widgets-dateinput-placeholder-month": "AAAA-MM",
- "api-error-blacklisted": "Trigue un titol diferent, mas descriptivo."
+ "mw-widgets-dateinput-placeholder-month": "AAAA-MM"
}
"createacct-yourpasswordagain-ph": "أدخل كلمة المرور مرة أخرى",
"userlogin-remembermypassword": "أبقني مسجلا للدخول",
"userlogin-signwithsecure": "الولوج باتصّال مؤمّن",
+ "cannotlogin-title": "لا يمكن تسجيل الدخول",
+ "cannotlogin-text": "تسجيل الدخول غير ممكن.",
"cannotloginnow-title": "لا يمكن تسجيل الدخول الآن",
"cannotloginnow-text": "لا يمكن تسجيل الدخول عند استخدام $1.",
+ "cannotcreateaccount-title": "لا يمكن إنشاء الحسابات",
+ "cannotcreateaccount-text": "إنشاء الحسابات المباشر غير مفعل على هذه الويكي.",
"yourdomainname": "نطاقك:",
"password-change-forbidden": "أنت لا يمكنك تغيير كلمات السر على هذا الويكي.",
"externaldberror": "هناك إما خطأ في دخول قاعدة البيانات الخارجية أو أنه غير مسموح لك بتحديث حسابك الخارجي.",
"botpasswords-updated-body": "كلمة سر البوت \"$1\" للمستخدم \"$2\" تم تحديثها.",
"botpasswords-deleted-title": "كلمة سر البوت حذفت",
"botpasswords-deleted-body": "كلمة سر البوت \"$1\" لمستخدم \"$2\" قد حذفت.",
- "botpasswords-newpassword": "كلمة السر الجديدة لتسجيل الدخول ب <strong>$1</strong> هي <strong>$2</strong>. <em>من فضلك سجل هذه كمرجع في المستقبل .</em>",
+ "botpasswords-newpassword": "كلمة السر الجديدة لتسجيل الدخول ب <strong>$1</strong> هي <strong>$2</strong>. <em>من فضلك سجل هذه كمرجع في المستقبل .</em><br> (للبوتات القديمة التي تتطلب أن يكون اسم تسجيل الدخول مثل اسم المستخدم النهائي، يمكنك أيضا استخدام <strong>$3</strong> كاسم مستخدم و <strong>$4</strong> ككلمة سر.)",
"botpasswords-no-provider": "BotPasswordsSessionProvider غير متاح.",
"botpasswords-restriction-failed": "قيود كلمة مرور البوت تمنع هذا الولوج.",
"botpasswords-invalid-name": "اسم المستخدم الموفر لا يحتوي على فاصل كلمة سر البوت (\"$1\").",
"invalid-content-data": "بيانات المحتوى غير صالحة",
"content-not-allowed-here": "\"$1\" المحتوى غير مسموح على صفحة [[$2]]",
"editwarning-warning": "مغادرة هذه الصفحة قد تتسبب بخسارتك لأي تغييرات أجريتها.\nإذا كنت مسجل الدخول، فيمكنك تعطيل هذا التحذير في قسم \"{{int:prefs-editing}}\" في تفضيلاتك.",
+ "editpage-invalidcontentmodel-title": "موديل المحتوى غير مدعوم",
+ "editpage-invalidcontentmodel-text": "موديل المحتوى \"$1\" غير مدعوم.",
"editpage-notsupportedcontentformat-title": "تنسيق المحتوى غير مدعوم",
"editpage-notsupportedcontentformat-text": "تنسيق المحتوى $1 غير مدعوم بواسطة نموذج المحتوى $2.",
"content-model-wikitext": "نص ويكي",
"print.css": "/* الأنماط المتراصة CSS المعروضة هنا ستؤثر على ناتج الطباعة */",
"noscript.css": "/* الأنماط المتراصة CSS المعروضة هنا ستؤثر على المستخدمين الذين الجافاسكريبت لديهم معطلة */",
"group-autoconfirmed.css": "/* الأنماط المتراصة CSS المعروضة هنا ستؤثر على المستخدمين المؤكدين تلقائيا فقط */",
+ "group-user.css": "/* CSS المعروض هنا سيؤثر على المستخدمين المسجلين فقط */",
"group-bot.css": "/* الأنماط المتراصة CSS المعروضة هنا ستؤثر على البوتات فقط */",
"group-sysop.css": "/* الأنماط المتراصة CSS المعروضة هنا ستؤثر على الإداريين فقط */",
"group-bureaucrat.css": "/* الأنماط المتراصة CSS المعروضة هنا ستؤثر على البيروقراطيين فقط */",
"common.js": "/* الجافاسكريبت الموضوع هنا سيتم تحميله لكل المستخدمين مع كل تحميل للصفحة. */",
"group-autoconfirmed.js": "/* أي جافاسكريبت هنا سيتم تحميلها للمستخدمين المؤكدين تلقائيا فقط */",
+ "group-user.js": "/* أي JavaScript هنا سيتم تحميله للمستخدمين المسجلين فقط */",
"group-bot.js": "/* أي جافاسكريبت هنا سيتم تحميلها للبوتات فقط */",
"group-sysop.js": "/* أي جافاسكريبت هنا سيتم تحميلها للإداريين فقط */",
"group-bureaucrat.js": "/* أي جافاسكريبت هنا سيتم تحميلها للبيروقراطيين فقط */",
"pageinfo-article-id": "معرف الصفحة (ID)",
"pageinfo-language": "لغة محتوى الصفحة",
"pageinfo-content-model": "نموذج محتوى الصفحة",
+ "pageinfo-content-model-change": "تغيير",
"pageinfo-robot-policy": "فهرسة الروبوتات",
"pageinfo-robot-index": "مسموح بها",
"pageinfo-robot-noindex": "غير مسموح بها",
"tag-filter": "مرشح [[Special:Tags|الوسوم]]:",
"tag-filter-submit": "مرشح",
"tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1||وسم|وسمان|وسوم}}]]: $2)",
+ "tag-mw-contentmodelchange": "تغيير موديل المحتوى",
+ "tag-mw-contentmodelchange-description": "التعديلات التي [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel تغير موديل المحتوى] لصفحة",
"tags-title": "وسوم",
"tags-intro": "هذه الصفحة تعرض الوسوم التي ربما يعلم البرنامج تعديلا بها، ومعانيها.",
"tags-tag": "اسم الوسم",
"tags-actions-header": "إجراءات",
"tags-active-yes": "نعم",
"tags-active-no": "لا",
- "tags-source-extension": "Ù\8aعرÙ\81Ù\87 اÙ\85تداد",
+ "tags-source-extension": "Ù\85عرÙ\81 بÙ\88اسطة اÙ\84برÙ\86اÙ\85ج",
"tags-source-manual": "تم تطبيقه يدويا بواسطة المستخدمين والبوتات.",
"tags-source-none": "لم يعد قيد الاستخدام",
"tags-edit": "عدل",
"createacct-yourpasswordagain-ph": "Escriba nuevamente la contraseña",
"userlogin-remembermypassword": "Caltener abierta la sesión",
"userlogin-signwithsecure": "Usar una conexón segura",
+ "cannotlogin-title": "Nun pudo aniciase sesión",
+ "cannotlogin-text": "Nun ye posible aniciar sesión.",
"cannotloginnow-title": "Nun puede aniciase sesión agora",
"cannotloginnow-text": "Nun puede aniciase sesión cuando s'usa $1.",
+ "cannotcreateaccount-title": "Nun pueden crease cuentes",
+ "cannotcreateaccount-text": "La creación direuta de cuentes nun ta activada nesta wiki.",
"yourdomainname": "El to dominiu:",
"password-change-forbidden": "Nun se pueden camudar les contraseñes nesta wiki.",
"externaldberror": "O hebo un fallu d'autenticación de la base de datos o nun tienes permisu p'anovar la to cuenta esterna.",
"botpasswords-updated-body": "Anovóse la contraseña del bot llamáu «$1» del usuariu «$2».",
"botpasswords-deleted-title": "Desanicióse la contraseña de bot",
"botpasswords-deleted-body": "Desanicióse la contraseña del bot llamáu «$1» del usuariu «$2».",
- "botpasswords-newpassword": "La nueva contraseña p'aniciar sesión con strong>$1</strong> ye <strong>$2</strong>. <em>Por favor, rexistra esto pa referencies futures.</em>",
+ "botpasswords-newpassword": "La nueva contraseña p'aniciar sesión con <strong>$1</strong> ye <strong>$2</strong>. <em>Por favor, rexistra esto pa referencies futures.</em> <br> (Pa los bots antiguos que necesiten que'l nome d'aniciu de sesión sía'l mesmu que'l nome d'usuariu, tamién pue usase <strong>$3</strong> como nome d'usuariu y <strong>$4</strong> como contraseña.)",
"botpasswords-no-provider": "BotPasswordsSessionProvider nun ta disponible.",
"botpasswords-restriction-failed": "Hai torgues de contraseña de bot que torgaron esti aniciu de sesión.",
"botpasswords-invalid-name": "El nome d'usuariu especificáu nun contien el separador de contraseña de bot («$1»).",
"invalid-content-data": "Datos del conteníu inválidos",
"content-not-allowed-here": "El conteníu «$1» nun se permite na páxina [[$2]]",
"editwarning-warning": "Salir d'esta páxina pue causar la perda de cualesquier cambiu fechu.\nSi anició sesión, pue desactivar esti avisu na seición «{{int:prefs-editing}}» de les preferencies.",
+ "editpage-invalidcontentmodel-title": "El modelu de conteníu nun tien sofitu",
+ "editpage-invalidcontentmodel-text": "El modelu de conteníu «$1»nun tien sofitu.",
"editpage-notsupportedcontentformat-title": "El formatu del conteníu nun tien sofitu",
"editpage-notsupportedcontentformat-text": "El formatu del conteníu, $1, nun tien sofitu del modelu de conteníu $2.",
"content-model-wikitext": "testu wiki",
"pageinfo-article-id": "ID de la páxina",
"pageinfo-language": "Llingua del conteníu de la páxina",
"pageinfo-content-model": "Plantía del conteníu de la páxina",
+ "pageinfo-content-model-change": "camudar",
"pageinfo-robot-policy": "Indexación por robots",
"pageinfo-robot-index": "Permitío",
"pageinfo-robot-noindex": "Torgao",
"tag-filter": "Filtru d'[[Special:Tags|etiquetes]]:",
"tag-filter-submit": "Peñera",
"tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Etiqueta|Etiquetes}}]]: $2)",
+ "tag-mw-contentmodelchange": "cambiu nel modelu de conteníu",
+ "tag-mw-contentmodelchange-description": "Ediciones que [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel camuden el modelu de conteníu] d'una páxina",
"tags-title": "Etiquetes",
"tags-intro": "Esta páxina llista les etiquetes coles que'l software pue marcar una edición, y el so significáu.",
"tags-tag": "Nome d'etiqueta",
"tags-actions-header": "Aiciones",
"tags-active-yes": "Sí",
"tags-active-no": "Non",
- "tags-source-extension": "Definida por una estensión",
+ "tags-source-extension": "Definío pol software",
"tags-source-manual": "Aplicada a mano polos usuarios y bots",
"tags-source-none": "Yá nun s'usa",
"tags-edit": "editar",
"createacct-yourpasswordagain-ph": "Увядзіце пароль зноў",
"userlogin-remembermypassword": "Запомніць мяне",
"userlogin-signwithsecure": "Скарыстацца бясьпечным злучэньнем",
+ "cannotlogin-title": "Немагчыма ўвайсьці",
+ "cannotlogin-text": "Уваход у сыстэму немагчымы.",
"cannotloginnow-title": "Цяпер немагчыма ўвайсьці",
"cannotloginnow-text": "Уваход у сыстэму немагчымы пры выкарыстаньні $1.",
+ "cannotcreateaccount-title": "Немагчыма стварыць рахункі",
+ "cannotcreateaccount-text": "Непасрэднае стварэньне рахункаў ня ўключана ў гэтай вікі.",
"yourdomainname": "Ваш дамэн:",
"password-change-forbidden": "Вы ня можаце зьмяняць паролі ў гэтай вікі.",
"externaldberror": "Адбылася памылка аўтэнтыфікацыі з дапамогай вонкавай базы зьвестак, ці Вам не дазволена абнаўляць свой рахунак.",
"botpasswords-updated-body": "Пароль робата для робата «$1» удзельніка «$2» быў абноўлены.",
"botpasswords-deleted-title": "Пароль робата выдалены",
"botpasswords-deleted-body": "Пароль робата для робата «$1» удзельніка «$2» быў выдалены.",
- "botpasswords-newpassword": "Новы пароль для ўваходу пад <strong>$1</strong> — <strong>$2</strong>. <em>Калі ласка, запішыце яго для далейшага выкарыстаньня.</em>",
+ "botpasswords-newpassword": "Новы пароль для ўваходу пад <strong>$1</strong> — <strong>$2</strong>. <em>Калі ласка, запішыце яго для далейшага выкарыстаньня.</em><br>(Для старых робатаў, якія патрабуюць, каб імя для ўваходу было аднолькавым з патэнцыйным імем удзельніка, вы можаце таксама карыстацца <strong>$3</strong> у якасьці імя і <strong>$4</strong> у якасьці паролю.)",
"botpasswords-no-provider": "BotPasswordsSessionProvider недаступны.",
"botpasswords-restriction-failed": "Уваход ня выкананы праз абмежаваньні на пароль робата",
"botpasswords-invalid-name": "Пададзенае імя ўдзельніка ня ўтрымлівае падзяляльнік для паролю робата («$1»).",
"changeemail-submit": "Зьмяніць адрас электроннай пошты",
"changeemail-throttled": "Вы зрабілі зашмат спробаў увайсьці ў сыстэму.\nКалі ласка, пачакайце $1 перад наступнай спробай.",
"changeemail-nochange": "Калі ласка, увядзіце іншы новы адрас электроннай пошты",
- "resettokens": "СкÑ\96дванÑ\8cне Ñ\82окенаÑ\9e",
+ "resettokens": "Скіданьне токенаў",
"resettokens-text": "Тут вы можаце скінуць токены, якія даюць вамд доступ да пэўных прыватных зьвестак, асацыяваных з вашым рахункам.\n\nКалі вы выпадкова падзяліліся токенамі зь іншымі, або калі ваш рахунак быў скампрамэтаваны, скарыстайцеся гэтай магчымасьцю і скіньце токены.",
"resettokens-no-tokens": "Няма што скідаць.",
"resettokens-tokens": "Токены:",
"invalid-content-data": "Няслушныя зьвесткі",
"content-not-allowed-here": "Зьмест тыпу «$1» на старонцы [[$2]] не дазволены",
"editwarning-warning": "Пакінуўшы гэтую старонку, вы можаце страціць усе ўнесеныя зьмены.\nКалі вы ўвайшлі ў сыстэму, Вы можаце адключыць гэтае папярэджаньне ў сэкцыі «{{int:prefs-editing}}» вашых наладаў.",
+ "editpage-invalidcontentmodel-title": "Мадэль зьместу не падтрымліваецца",
+ "editpage-invalidcontentmodel-text": "Мадэль зьместу «$1» не падтрымліваецца.",
"editpage-notsupportedcontentformat-title": "Фармат зьмесьціва не падтрымліваецца",
"editpage-notsupportedcontentformat-text": "Фармат зьмесьціва $1 не падтрымліваецца мадэльлю зьмесьціва $2.",
"content-model-wikitext": "вікі-тэкст",
"fileexists-forbidden": "Файл з такой назвай ужо існуе і ня можа быць перапісаны.\nКалі ласка, вярніцеся назад і загрузіце гэты файл з новай назвай. [[File:$1|thumb|center|$1]]",
"fileexists-shared-forbidden": "Файл з такой назвай ужо існуе ў агульным сховішчы файлаў.\nКалі Вы жадаеце загрузіць Ваш файл, вярніцеся назад і загрузіце гэты файл з новай назвай. [[File:$1|thumb|center|$1]]",
"fileexists-no-change": "Гэтая загрузка зьяўляецца дакладнай копіяй цяперашняй вэрсіі <strong>[[:$1]]</strong>.",
+ "fileexists-duplicate-version": "Гэтая загрузка зьяўляецца дакладнай копіяй {{PLURAL:$2|1=старой вэрсіі|старых вэрсіяў}} файлу <strong>[[:$1]]</strong>.",
"file-exists-duplicate": "Гэты файл дублюе {{PLURAL:$1|1=наступны файл|наступныя файлы}}:",
"file-deleted-duplicate": "Падобны файл ([[:$1]]) ужо выдаляўся. Калі ласка, паглядзіце гісторыю выдаленьняў гэтага файла перад яго паўторнай загрузкай.",
"file-deleted-duplicate-notitle": "Файл, ідэнтычны гэтаму файлу, раней ужо быў выдалены, а назва файла была забароненая.\nВам трэба зьвярнуцца да некага з правамі прагляду зьвестак забароненых файлаў, каб прааналізаваць сытуацыю перад тым, як загружаць файл ізноў.",
"pageinfo-article-id": "Ідэнтыфікатар старонкі",
"pageinfo-language": "Мова зьместу старонкі",
"pageinfo-content-model": "Мадэль зьместу старонкі",
+ "pageinfo-content-model-change": "зьмяніць",
"pageinfo-robot-policy": "Індэксацыя пашукавікамі",
"pageinfo-robot-index": "Дазволеная",
"pageinfo-robot-noindex": "Не дазволеная",
"tags-actions-header": "Дзеяньні",
"tags-active-yes": "Так",
"tags-active-no": "Не",
- "tags-source-extension": "Вызначаецца пашырэньнем",
+ "tags-source-extension": "Вызначаецца праграмным забесьпячэньнем",
"tags-source-manual": "Ставіцца ўручную ўдзельнікамі і робатамі",
"tags-source-none": "Больш не выкарыстоўваецца",
"tags-edit": "рэдагаваць",
"createacct-yourpasswordagain-ph": "Увядзіце пароль яшчэ раз",
"userlogin-remembermypassword": "Заставацца ў сістэме",
"userlogin-signwithsecure": "Выкарыстоўваць абароненае злучэнне",
+ "cannotlogin-title": "Немагчыма ўвайсці",
+ "cannotlogin-text": "Уваход у сістэму немагчымы.",
"cannotloginnow-title": "Зараз немагчыма ўвайсці",
"cannotloginnow-text": "Пры выкарыстанні $1 немагчыма прадставіцца сістэме.",
+ "cannotcreateaccount-title": "Немагчыма стварыць уліковыя запісы",
+ "cannotcreateaccount-text": "Непасрэднае стварэнне ўліковых запісаў не ўключана на гэтай вікі.",
"yourdomainname": "Ваш дамен:",
"password-change-forbidden": "Вы не можаце змяняць паролі на гэтай Вікі.",
"externaldberror": "Або памылка вонкавай аўтэнтыкацыі ў базе дадзеных, або вам не дазволена абнаўляць свой вонкавы рахунак.",
"uploaded-href-attribute-svg": "у SVG файлах атрыбутам href дазволены толькі мэты віду http:// або https://, знойдзена <code><$1 $2=\"$3\"></code>.",
"uploaded-href-unsafe-target-svg": "У ўкладзеным SVG файле знойдзена спасылка на небяспечныя звесткі: URI мэты <code><$1 $2=\"$3\"></code>.",
"uploaded-animate-svg": "У ўкладзеным SVG файле знойдзены тэг \"animate\", здольны змяніць спасылку з дапамогай атрыбута \"from\" <code><$1 $2=\"$3\"></code>.",
+ "uploaded-setting-event-handler-svg": "Устаноўка атрыбутаў апрацоўкі падзей заблакавана, у ўкладзеным SVG-файле знойдзены код <code><$1 $2=\"$3\"></code>.",
+ "uploaded-setting-href-svg": "Выкарыстанне тэга \"set\" для дадання атрыбута \"href\" у бацькоўскі элемент заблакавана.",
"uploadscriptednamespace": "Гэты файл SVG утрымлівае недапушчальную прастору імёнаў \"$1\".",
"uploadinvalidxml": "Немагчыма прааналізаваць XML ва ўкладзеным файле.",
"uploadvirus": "Файл утрымлівае вірус! Падрабязнасці: $1",
"Macofe",
"Bodhisattwa",
"Matma Rex",
- "আজিজ"
+ "আজিজ",
+ "Kayser Ahmad"
]
},
"tog-underline": "সংযোগগুলির নিচে দাগ দেখানো হোক:",
"createacct-yourpasswordagain-ph": "আবারও পাসওয়ার্ড লিখুন",
"userlogin-remembermypassword": "আমাকে প্রবেশ অবস্থায় রাখো",
"userlogin-signwithsecure": "নিরাপদ সংযোগ ব্যবহার করুন",
+ "cannotlogin-title": "প্রবেশ করতে পারবেন না",
"cannotloginnow-title": "এখন প্রবেশ করা যাবে না",
"cannotloginnow-text": "$1 ব্যবহার করার সময় প্রবেশ করা সম্ভব নয়।",
+ "cannotcreateaccount-title": "অ্যাকাউন্ট তৈরি করা যাবে না",
"yourdomainname": "আপনার ডোমেইন:",
"password-change-forbidden": "আপনি এই উইকিতে পাসওয়ার্ড পরিবর্তন করতে পারবেন না।",
"externaldberror": "হয় কোন বহিঃস্থ যাচাইকরণ ডাটাবেজ ত্রুটি ঘটেছে অথবা আপনার বহিঃস্থ অ্যাকাউন্ট হালনাগাদ করার অনুমতি নেই।",
"grant-editprotected": "সংরক্ষিত পাতা সম্পাদনা করুন",
"grant-privateinfo": "ব্যক্তিগত তথ্যে প্রবেশাধিকার",
"grant-sendemail": "অন্য ব্যবহারকারীকে ইমেইল পাঠান",
+ "grant-uploadeditmovefile": "ফাইল আপলোড, প্রতিস্থাপন এবং স্থানান্তর",
"grant-uploadfile": "নতুন ফাইল আপলোড করুন",
"grant-basic": "মৌলিক অধিকার",
"grant-viewdeleted": "অপসারিত ফাইল ও পাতাগুলি দেখুন",
"apisandbox-retry": "পুনঃচেষ্টা করুন",
"apisandbox-loading": "\"$1\" এপিআই মডিউলের জন্য তথ্য লোড হচ্ছে...",
"apisandbox-load-error": "\"$1\" এপিআই মডিউলের জন্য তথ্য লোড করার সময় একটি ত্রুটি ঘটেছে: $2",
+ "apisandbox-no-parameters": "এই API মডিউলের কোন প্যারামিটার নেই।",
"apisandbox-helpurls": "সাহায্যকারী লিঙ্কসমূহ",
"apisandbox-examples": "উদাহরণ",
"apisandbox-dynamic-parameters": "অতিরিক্ত প্যারামিটার",
"pageinfo-article-id": "পাতার আইডি",
"pageinfo-language": "পাতার তথ্যের ভাষা",
"pageinfo-content-model": "পাতার বিষয়বস্তুর মডেল",
+ "pageinfo-content-model-change": "পরিবর্তন",
"pageinfo-robot-policy": "রোবটের মাধ্যমে ইন্ডেক্স করা হচ্ছে",
"pageinfo-robot-index": "অনুমোদিত",
"pageinfo-robot-noindex": "অনুনমোদিন",
"tags-actions-header": "কার্যসমূহ",
"tags-active-yes": "হ্যাঁ",
"tags-active-no": "না",
- "tags-source-extension": "à¦\8fà¦\95à¦\9fি à¦\8fà¦\95à§\8dসà¦\9fà§\87নশন দ্বারা সংজ্ঞায়িত",
+ "tags-source-extension": "সফà¦\9fà¦\93য়à§\8dযার দ্বারা সংজ্ঞায়িত",
"tags-source-manual": "ব্যবহারকারী এবং বট দ্বারা ম্যানুয়ালি প্রয়োগ",
"tags-source-none": "আর ব্যবহার করা হচ্ছে না",
"tags-edit": "সম্পাদনা",
"tags-delete-submit": "অপরিবর্তনীয় এই ট্যাগ অপসারন করো",
"tags-delete-not-found": "\"$1\" ট্যাগ বিদ্যমান নয়।",
"tags-activate-title": "সক্রিয় ট্যাগ",
+ "tags-activate-question": "আপনি ট্যাগ \"$1\" সক্রিয় করতে চলেছেন।",
"tags-activate-reason": "কারণ:",
+ "tags-activate-not-allowed": "ট্যাগ \"$1\" সক্রিয় করা সম্ভব নয়।",
+ "tags-activate-not-found": "\"$1\" ট্যাগের অস্তিত্ব নেই।",
"tags-activate-submit": "চালু",
"tags-deactivate-title": "নিষ্ক্রিয় ট্যাগ",
+ "tags-deactivate-question": "আপনি ট্যাগ \"$1\" নিষ্ক্রিয় করতে চলেছেন।",
"tags-deactivate-reason": "কারণ:",
+ "tags-deactivate-not-allowed": "ট্যাগ \"$1\" নিষ্ক্রিয় করা সম্ভব নয়।",
"tags-deactivate-submit": "নিষ্ক্রিয়",
"tags-edit-title": "ট্যাগ সম্পাদনা করুন",
"tags-edit-manage-link": "ট্যাগ পরিচালনা করুন",
"authform-wrongtoken": "ভুল টোকেন",
"specialpage-securitylevel-not-allowed-title": "অনুমতি নেই",
"specialpage-securitylevel-not-allowed": "দুঃখিত, আপনি এই পাতা ব্যবহার করতে অনুমতিপ্রাপ্ত নন কারণ আপনার পরিচয় যাচাই করা যায়নি।",
+ "authpage-cannot-login": "প্রবেশ শুরু করা সম্ভন নয়।",
"cannotauth-not-allowed-title": "অনুমতি অস্বীকৃত",
"cannotauth-not-allowed": "আপনি এই পাতাটি ব্যবহার করতে অনুমতিপ্রাপ্ত নন।",
"changecredentials": "পরিচয়পত্র পরিবর্তন করুন",
"yourpasswordagain": "Skrivit ho ker-tremen en-dro",
"createacct-yourpasswordagain": "Kadarnaat ar ger-tremen",
"createacct-yourpasswordagain-ph": "Skrivit ar ger-tremen adarre",
- "remembermypassword": "Derc'hel soñj eus ma ger-tremen war an urzhiataer-mañ (evit $1 devezh{{PLURAL:$1||}} d'ar muiañ)",
"userlogin-remembermypassword": "Derc'hel ac'hanon kevreet",
"userlogin-signwithsecure": "Implijout ur gevreadenn suraet",
"yourdomainname": "Ho tomani",
"minoredit": "Kemm dister",
"watchthis": "Evezhiañ ar pennad-mañ",
"savearticle": "Enrollañ ar bajenn",
+ "savechanges": "Enrollañ ar c'hemmoù",
+ "publishpage": "Embann ar bajenn",
+ "publishchanges": "Embann ar c'hemmoù",
"preview": "Rakwelet",
"showpreview": "Rakwelet",
"showdiff": "Diskouez ar c'hemmoù",
"tooltip-pt-anontalk": "Kaozeadennoù diwar-benn ar c'hemmoù graet adal ar chomlec'h-mañ",
"tooltip-pt-preferences": "{{GENDER:|Ma}} fenndibaboù",
"tooltip-pt-watchlist": "Roll ar pajennoù evezhiet ganeoc'h.",
- "tooltip-pt-mycontris": "Roll ho tegasadennoù{{GENDER:|your}}",
+ "tooltip-pt-mycontris": "Roll ho tegasadennoù",
"tooltip-pt-login": "Daoust ma n'eo ket ret, ec'h aliomp deoc'h kevreañ",
"tooltip-pt-logout": "Digevreañ",
"tooltip-pt-createaccount": "Erbedet eo deoc'h krouiñ ur gont ha kevreañ ; n'eo ket ret koulskoude.",
"rev-deleted-user": "(korisničko ime uklonjeno)",
"rev-deleted-event": "(stavka zapisa obrisana)",
"rev-deleted-user-contribs": "[korisničko ime ili IP adresa uklonjeni - izmjena sakrivena u spisku doprinosa]",
- "rev-deleted-text-permission": "Revizija ove stranice je '''obrisana'''.\nDetalje možete vidjeti u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisu brisanja].",
+ "rev-deleted-text-permission": "Revizija ove stranice je '''obrisana'''.\nDetalje možete vidjeti u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisniku brisanja].",
"rev-suppressed-text-permission": "Revizija ove stranice je <strong>prekrivena</strong>.\nDetalji se mogu naći u [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zapisniku prekrivanja].",
- "rev-deleted-text-unhide": "Revizija ove stranice je '''obrisana'''.\nDetalje o tome može se vidjeti u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisniku brisanja].\nVi je i dalje možete [$1 vidjeti ovu reviziju] ako želite da nastavite.",
- "rev-suppressed-text-unhide": "Ova revizija stranice je '''uklonjena'''.\nMožete pogledati detalje u [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zapisu uklanjanja].\nVi je i dalje možete [$1 vidjeti ovu reviziju] ako želite.",
- "rev-deleted-text-view": "Revizija ove stranice je '''obrisana'''.\nVi je možete vidjeti; detalji o tome se mogu vidjeti u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisu brisanja].",
+ "rev-deleted-text-unhide": "Izmjena ove stranice je <strong>obrisana</strong>.\nDetalje možete vidjeti u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisniku brisanja].\nIpak možete [$1 vidjeti ovu izmjenu] ako želite nastaviti.",
+ "rev-suppressed-text-unhide": "Ova revizija stranice je '''uklonjena'''.\nMožete pogledati detalje u [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zapisu uklanjanja].\nVi i dalje možete [$1 vidjeti ovu reviziju] ako želite.",
+ "rev-deleted-text-view": "Revizija ove stranice je '''obrisana'''.\nVi je možete vidjeti; detalji o tome mogu se vidjeti u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisniku brisanja].",
"rev-suppressed-text-view": "Ova revizija stranice je '''uklonjena'''.\nVi je možete vidjeti; možete pogledati detalje u [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zapisu uklanjanja].",
"rev-deleted-no-diff": "Ne možete vidjeti ovu razliku jer je jedna od izmjena '''obrisana'''.\nDetalji se nalaze u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisniku brisanja].",
"rev-suppressed-no-diff": "Ne možete vidjeti ove razlike jer je jedna od revizija '''obrisana'''.",
- "rev-deleted-unhide-diff": "Jedna od revizija u ovom pregledu razlika je '''obrisana'''.\nMožete pregledati detalje u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisniku brisanja].\nVi još uvijek možete [$1 vidjeti ove razlike] ako želite da nastavite.",
- "rev-suppressed-unhide-diff": "edna od revizija ove razlike je '''uklonjena'''.\nMožete pogledati detalje u [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zapisniku uklanjanja].\nVi i dalje možete [$1 vidjeti ove razlike] ako želite da nastavite.",
- "rev-deleted-diff-view": "Jedna od revizija u ovoj razlici je '''obrisana'''.\nVi možete vidjeti ovu razliku; detalji o tome se mogu vidjeti u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisniku brisanja].",
+ "rev-deleted-unhide-diff": "Jedna od revizija u ovom pregledu razlika je '''obrisana'''.\nMožete pregledati detalje u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisniku brisanja].\nVi još uvijek možete [$1 vidjeti ove razlike] ako želite nastaviti.",
+ "rev-suppressed-unhide-diff": "Jedna od revizija ove razlike je '''uklonjena'''.\nMožete pogledati detalje u [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zapisniku uklanjanja].\nVi i dalje možete [$1 vidjeti ove razlike] ako želite nastaviti.",
+ "rev-deleted-diff-view": "Jedna od revizija u ovoj razlici je '''obrisana'''.\nVi možete vidjeti ovu razliku; detalji o tome mogu se vidjeti u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisniku brisanja].",
"rev-suppressed-diff-view": "Jedna od revizija u ovoj razlici je '''sakrivena'''.\nVi možete vidjeti ovu razliku; detalji se mogu vidjeti u [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zapisniku sakrivanja].",
"rev-delundel": "pokaži/sakrij",
"rev-showdeleted": "prikaži",
"actionthrottled": "چالاکی پێشی پێ گیرا",
"actionthrottledtext": "بە مەبەستی پێشگریی لە سپەم، ڕێگە نادرێت تۆ لە ماوەیەکی کورت دا لە سەر یەک ئەمە زۆر جار ئەنجام بدەی، وە ئیستا تۆ لە ڕادە بەدەرت کردووە.\nتکایە پاش چەند خولەک دووبارە تاقی بکەوە.",
"protectedpagetext": "بۆ بەرگری لە دەستکاریکردن یان چالاکییەکانی تر ئەم پەڕەیە پارێزراوە.",
- "viewsourcetext": "دەتوانی سەرچاوەی ئەم پەڕە ببینی و کۆپیی بکەی:",
- "viewyourtext": "دەتوانی ژێدەری '''دەستکارییەکەت''' لەم پەڕەیەدا ببینی و کۆپی بکەی:",
+ "viewsourcetext": "دەتوانی سەرچاوەی ئەم پەڕە ببینی و کۆپیی بکەی٫",
+ "viewyourtext": "دەتوانی ژێدەری <strong>دەستکارییەکەت</strong> لەم پەڕەیەدا ببینی و کۆپی بکەی.",
"protectedinterface": "ئەم پەڕەیە دەقی ڕواڵەتی نەرمامێری ئەم ویکییە نیشان دەدات و بۆ بەرگری لە خراپکاری پارێزراوە.\nبۆ زیادکردن یان گۆڕینی وەرگێڕانەکان بۆ ھەموو ویکییەکان، تکایە لە [https://translatewiki.net/ translatewiki.net]، پرۆژەی ناوچەیی کردنی میدیاویکی کەڵک وەربگرە.",
"editinginterface": "<strong>ھۆشیار بە:</strong> خەریکی دەستکاریی پەڕەیەک دەکەیت کە بۆ دابین کردنی دەقی ڕووکاری نەرمامێر بەکاردێت.\nگۆڕانکارییەکان لەم پەڕەیەدا لە سەر ڕواڵەتی پەڕەکان بۆ بەکارھێنەرانی تر لەم ویکییەدا کاریگەر دەبێت.",
"cascadeprotected": "ئەم لاپەڕە پارێزراوە لە دەستکاریی، چونکا خراوەتە سەر ڕیزی ئەم {{PLURAL:$1|لاپەڕانه، کە}} که به ههڵکردنی بژاردهی داڕژان ههڵکراوه:\n$2",
"newpassword": "تێپەڕوشەی نوێ:",
"retypenew": "تێپەڕوشەی نوێ دوبارە بنووسەوە:",
"resetpass_submit": "تێپەڕوشە رێکخە و بچۆ ژوورەوە",
- "changepassword-success": "تێپەروشەکەت بە سەرکەوتوویی گۆڕدرا!",
+ "changepassword-success": "تێپەڕەوشەکەت گۆڕدرا!",
"botpasswords-label-create": "دروستکردن",
"botpasswords-label-update": "نوێکردنەوە",
"botpasswords-label-cancel": "ھەڵوەشاندنەوە",
"prefs-watchlist-token": "ڕەمزی لیستی چاودێری:",
"prefs-misc": "جۆراوجۆر",
"prefs-resetpass": "تێپەڕوشە بگۆڕە",
- "prefs-changeemail": "ئەدرەسی ئیمەیل بگۆڕە",
+ "prefs-changeemail": "ئەدرەسی ئیمەیل بگۆڕە یان لایبەرە",
"prefs-setemail": "ناونیشانێکی ئیمەیل دیاری بکە",
"prefs-email": "ھەڵبژاردەکانی ئیمەیل",
"prefs-rendering": "ڕواڵەت",
"rollbackfailed": "گەڕاندنەوە سەرکەوتوو نەبوو",
"cantrollback": "دەستکاریەکان ناگەڕێندرێتەوە؛\nدوایین هاوبەش تەنها ڕێکخەری ئەم لاپەڕەیە.",
"alreadyrolled": "دوایین گۆڕانکارییەکان لەسەر [[:$1]] لە لایەن [[User:$2|$2]] ناگەڕێندرێنەوە ([[User talk:$2|لێدوان]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]])؛ کەسێکی تر لە پێشدا دەستکاریی کردووە یان گەڕاندوویەتەوە.\n\nدوایین دەستکاری ئەم پەڕە [[User:$3|$3]] کردوویە ([[User talk:$3|لێدوان]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
- "editcomment": "پوختەی دەستکاری \"''$1''\" بوو.",
+ "editcomment": "پوختەی دەستکاری <em>$1</em> بوو.",
"revertpage": "گەڕاندنەوەی دەستکارییەکانی [[Special:Contributions/$2|$2]] ([[User talk:$2|لێدوان]]) بۆ دوایین پێداچوونەوەی [[User:$1|$1]]",
"revertpage-nouser": "دەستکارییەکانی بەکارھێنەرێکی شاڕدراوە بۆ دوایین پێداچوونەوەی {{GENDER:$1|[[User:$1|$1]]}} گەڕێنرایەوە.",
"rollback-success": "دەستکارییەکانی $1 وەرگێرایەوە؛<br />\nگۆڕدرا بۆ دوایین پێداچوونەوەی $2.",
"sp-contributions-newbies-sub": "بۆ ھەژمارە نوێکان",
"sp-contributions-newbies-title": "بەشدارییەکانی بەکارھێنەر بۆ ھەژمارە نوێکان",
"sp-contributions-blocklog": "لۆگی بەربەستن",
- "sp-contributions-deleted": "بەشدارییە سڕاوەکان",
+ "sp-contributions-deleted": "بەشدارییە سڕاوەکانی {{GENDER:$1|بەکارھێنەر}}",
"sp-contributions-uploads": "بارکردنەکان",
"sp-contributions-logs": "لۆگەکان",
"sp-contributions-talk": "لێدوان",
"whatlinkshere-next": "{{PLURAL:$1|دیکە|$1ی تر}}",
"whatlinkshere-links": "← بەستەرەکان",
"whatlinkshere-hideredirs": "ڕەوانەکەرەکان $1",
- "whatlinkshere-hidetrans": "$1 ھێنانەناوەوەکان",
+ "whatlinkshere-hidetrans": "ھێنانەناوەوەکان $1",
"whatlinkshere-hidelinks": "$1 بەستەر",
"whatlinkshere-hideimages": "$1 بەستەرەکانی پەڕگە",
"whatlinkshere-filters": "پاڵێوکەکان",
"tooltip-feed-rss": "RSS feed بۆ ئەم پەڕە",
"tooltip-feed-atom": "Atom feed بۆ ئەم پەڕە",
"tooltip-t-contributions": "پێڕستی بەشدارییەکانی {{GENDER:$1|ئەم بەکارھێنەرە}}",
- "tooltip-t-emailuser": "ئیمەیلێک بنێرە بۆ ئەم بەکارھێنەرە",
+ "tooltip-t-emailuser": "ئیمەیڵێک بنێرە بۆ {{GENDER:$1|ئەم بەکارھێنەرە}}",
"tooltip-t-upload": "پەڕگە بار بکە",
"tooltip-t-specialpages": "پێڕستی ھەموو پەڕە تایبەتەکان",
"tooltip-t-print": "وەشانی چاپی ئەم پەڕەیە",
"lastmodifiedatby": "ئەم پەڕە دواجار لە $2ی $1 بە دەستی $3 گۆڕدراوە.",
"othercontribs": "لەسەر بنەمای کاری $1.",
"others": "ئەوانی دیکە",
- "siteusers": "{{PLURAL:$2|بەکارھێنەری|بەکارھێنەرانی}} {{SITENAME}} $1",
+ "siteusers": "{{SITENAME}} {{PLURAL:$2|{{GENDER:$1|بەکارھێنەری}}|بەکارھێنەرانی}} $1",
"anonusers": "{{PLURAL:$2|بەکارھێنەر|بەکارھێنەر}}ی نامۆی {{SITENAME}} $1",
"creditspage": "بایەخەکانی لاپەڕە",
"nocredits": "هیچ زانیارییەکی بایەخ لەبەردەستدا نیە بۆ ئەم لاپەڕە.",
"fileduplicatesearch-result-n": "پەڕگەی «$1» {{PLURAL:$2|١ دووپاتکراوەی کوتوموتی|$2 دووپاتکراوەی کوتوموتی}} ھەیە.",
"fileduplicatesearch-noresults": "پەڕگەیەک بە ناوی «$1» نەدۆزرایەوە.",
"specialpages": "پەڕە تایبەتەکان",
- "specialpages-note": "* Ù¾Û\95Ú\95Û\95 تاÛ\8cبÛ\95تÛ\95 ئاساÛ\8cÛ\8cÛ\8cÛ\95کاÙ\86.\n* <span class=\"mw-specialpagerestricted\">Ù¾Û\95Ú\95Û\95 تاÛ\8cبÛ\95تÛ\95 بÛ\95رگرÛ\8câ\80\8cÙ\84Û\8eکراÙ\88Û\95کاÙ\86.</span>",
+ "specialpages-note": "* Ù¾Û\95Ú\95Û\95 تاÛ\8cبÛ\95تÛ\95 ئاساÛ\8cÛ\8cÛ\95کاÙ\86.\n* <span class=\"mw-specialpagerestricted\">Ù¾Û\95Ú\95Û\95 تاÛ\8cبÛ\95تÛ\95 بÛ\95رگرÛ\8cÙ\84Û\8eکراÙ\88Û\95کاÙ\86.</span>",
"specialpages-group-maintenance": "ڕاپۆرتەکانی چاکسازی",
"specialpages-group-other": "پەڕە تایبەتەکانی دیکە",
"specialpages-group-login": "چوونەژوورەوە / دروستکردنی ھەژمار",
"compare-invalid-title": "ئەم سەردێڕە دەستنیشانت کردووە نادروستە.",
"dberr-problems": "ببورە! ئەم ماڵپەڕە ئێستا خەریک ئەزموونێکی کێشەی تەکنیکیە.",
"dberr-again": "چەن خولک ڕاوەستە و نوێی بکەوە.",
- "dberr-info": "(Ù¾Û\95Û\8cÙ\88Û\95Ù\86دÛ\8c Ù\84Û\95Ú¯Û\95Úµ Ú\95اÚ\98Û\95کارÛ\8c بÙ\86Ú©Û\95دراÙ\88 Ù¾Û\8eÚ©Ù\86اÛ\8cÛ\95ت: $1)",
+ "dberr-info": "(Ù\86اتÙ\88اÙ\86Û\8cت بگÛ\95Û\8cت بÛ\95 بÙ\86Ú©Û\95دراÙ\88: $1)",
"dberr-usegoogle": "دەتوانی هاوکات هەوڵی گەڕان بە گووگڵ بدەیت.",
"dberr-outofdate": "لەیادت بێ لەوانەیە پێرستەکەیان سەبارەت نە ناوەڕۆک ئەم ماڵپەڕە ماوە بەسەرچوو بێت.",
"dberr-cachederror": "ئەمە ڕوونووسێکی کاشکراوی لاپەڕەی داواکراوە و لەوانەیە بەڕۆژ نەبێت.",
"logentry-newusers-autocreate": "ھەژماری بەکارھێنەریی $1 بە شێوەی خۆگەڕ {{GENDER:$2|دروست کرا}}",
"logentry-protect-protect": "$1 $3ی {{GENDER:$2|پاراست}} $4",
"logentry-protect-modify": "$1 ئاستی پاراستنی $3ی {{GENDER:$2|گۆڕی}} $4",
- "logentry-rights-rights": "$1 ئەندامێتیی $3ی لە $4 بۆ $5 {{GENDER:$2|گۆڕی}}",
+ "logentry-rights-rights": "$1 ئەندامێتیی {{GENDER:$6|$3}}ی لە $4 بۆ $5 {{GENDER:$2|گۆڕی}}",
"logentry-upload-upload": "$1 $3ی {{GENDER:$2|بار کرد}}",
"logentry-upload-overwrite": "$1 وەشانێکی نوێی $3ی {{GENDER:$2|بار کرد}}",
"rightsnone": "(ھیچ)",
"createacct-yourpasswordagain-ph": "Zadejte heslo ještě jednou",
"userlogin-remembermypassword": "Přihlásit trvale",
"userlogin-signwithsecure": "Používat zabezpečené připojení",
+ "cannotlogin-title": "Nelze se přihlásit",
+ "cannotlogin-text": "Přihlášení není možné.",
"cannotloginnow-title": "Momentálně se nelze přihlásit",
"cannotloginnow-text": "Přihlášení není možné, když se používají $1.",
+ "cannotcreateaccount-title": "Nelze zakládat uživatelské účty",
+ "cannotcreateaccount-text": "Přímé zakládání účtů není na této wiki povoleno.",
"yourdomainname": "Vaše doména",
"password-change-forbidden": "Na této wiki nemůžete měnit hesla.",
"externaldberror": "Buď nastala chyba externí autentizační databáze, nebo nemáte dovoleno měnit svůj externí účet.",
"botpasswords-updated-body": "Heslo pro bota jménem „$1“ {{GENDER:$2|uživatele|uživatelky}} „$2“ bylo aktualizováno.",
"botpasswords-deleted-title": "Heslo pro bota smazáno",
"botpasswords-deleted-body": "Heslo pro bota jménem „$1“ {{GENDER:$2|uživatele|uživatelky}} „$2“ bylo smazáno.",
- "botpasswords-newpassword": "Nové přihlašovací heslo pro bota <strong>$1</strong> je <strong>$2</strong>. <em>Zaznamenejte si je pro budoucí použití.</em>",
+ "botpasswords-newpassword": "Nové přihlašovací heslo pro bota <strong>$1</strong> je <strong>$2</strong>. <em>Zaznamenejte si je pro budoucí použití.</em> <br> (Pro staré boty, vyžadující, aby přihlašovací jméno bylo stejné jako následné uživatelské jméno, můžete také jako uživatelské jméno použít <strong>$3</strong> a jako heslo <strong>$4</strong>.)",
"botpasswords-no-provider": "BotPasswordsSessionProvider není dostupný.",
"botpasswords-restriction-failed": "Toto přihlášení bylo zamítnuto omezením hesel pro boty.",
"botpasswords-invalid-name": "Uvedené uživatelské jméno neobsahuje oddělovač hesel pro boty („$1“).",
"invalid-content-data": "Obsažená data jsou chybná",
"content-not-allowed-here": "Obsah typu $1 není na stránce [[$2]] dovolen.",
"editwarning-warning": "Opuštěním této stránky se mohou veškeré provedené změny ztratit.\nPřihlášení uživatelé si mohou toto varování vypnout na záložce „{{int:prefs-editing}}“ v uživatelském nastavení.",
+ "editpage-invalidcontentmodel-title": "Nepodporovaný model obsahu",
+ "editpage-invalidcontentmodel-text": "Model obsahu „$1“ není podporován.",
"editpage-notsupportedcontentformat-title": "Nepodporovaný formát obsahu",
"editpage-notsupportedcontentformat-text": "Model obsahu $2 nepodporuje formát obsahu $1.",
"content-model-wikitext": "wikitext",
"pageinfo-article-id": "ID stránky",
"pageinfo-language": "Jazyk obsahu stránky",
"pageinfo-content-model": "Model obsahu stránky",
+ "pageinfo-content-model-change": "změnit",
"pageinfo-robot-policy": "Indexování roboty",
"pageinfo-robot-index": "Dovoleno",
"pageinfo-robot-noindex": "Zakázáno",
"tag-filter": "Filtr podle [[Special:Tags|značek]]:",
"tag-filter-submit": "Filtrovat",
"tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Značka|Značky}}]]: $2)",
+ "tag-mw-contentmodelchange": "změna modelu obsahu",
+ "tag-mw-contentmodelchange-description": "Editace, které [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel mění obsah modelu] stránky",
"tags-title": "Značky",
"tags-intro": "Tato stránka obsahuje seznam značek, kterými může software označovat jednotlivé editace, a jejich významy.",
"tags-tag": "Název značky",
"tags-actions-header": "Akce",
"tags-active-yes": "Ano",
"tags-active-no": "Ne",
- "tags-source-extension": "Definována rozšířením",
+ "tags-source-extension": "Definována softwarem",
"tags-source-manual": "Přidávána ručně uživateli a boty",
"tags-source-none": "Už nepoužívána",
"tags-edit": "editovat",
"createacct-yourpasswordagain-ph": "Gib das Passwort erneut ein",
"userlogin-remembermypassword": "Angemeldet bleiben",
"userlogin-signwithsecure": "Sichere Verbindung verwenden",
+ "cannotlogin-title": "Die Anmeldung ist nicht möglich.",
+ "cannotlogin-text": "Die Anmeldung ist nicht möglich.",
"cannotloginnow-title": "Anmeldung nicht erfolgreich",
"cannotloginnow-text": "Eine Anmeldung ist mit Verwendung von $1 nicht möglich.",
+ "cannotcreateaccount-title": "Die Erstellung von Benutzerkonten ist nicht möglich.",
+ "cannotcreateaccount-text": "Die direkte Erstellung von Benutzerkonten ist auf diesem Wiki nicht aktiviert.",
"yourdomainname": "Deine Domain:",
"password-change-forbidden": "Du kannst auf diesem Wiki keine Passwörter ändern.",
"externaldberror": "Entweder liegt ein Fehler bei der externen Authentifizierung vor oder du darfst dein externes Benutzerkonto nicht aktualisieren.",
"botpasswords-updated-body": "Das Botpasswort für den Botnamen „$1“ des Benutzers „$2“ wurde aktualisiert.",
"botpasswords-deleted-title": "Botpasswort gelöscht",
"botpasswords-deleted-body": "Das Botpasswort für den Botnamen „$1“ des Benutzers „$2“ wurde gelöscht.",
- "botpasswords-newpassword": "Das neue Passwort zur Anmeldung mit <strong>$1</strong> ist <strong>$2</strong>. <em>Bitte halte dies für die Zukunft fest.</em>",
+ "botpasswords-newpassword": "Das neue Passwort zur Anmeldung mit <strong>$1</strong> ist <strong>$2</strong>. <em>Bitte halte dies für die Zukunft fest.</em><br>Für alte Bots, die erfordern, dass der Anmeldename mit dem späteren Benutzernamen identisch ist, kannst du auch <strong>$3</strong> als Benutzernamen und <strong>$4</strong> als Passwort verwenden.",
"botpasswords-no-provider": "BotPasswordsSessionProvider ist nicht verfügbar.",
"botpasswords-restriction-failed": "Beschränkungen des Botpassworts verhindern diese Anmeldung.",
"botpasswords-invalid-name": "Der angegebene Benutzername enthält keinen Botpassworttrenner („$1“).",
"invalid-content-data": "Ungültige Inhaltsdaten",
"content-not-allowed-here": "Der Inhalt „$1“ ist auf der Seite [[$2]] nicht erlaubt",
"editwarning-warning": "Das Verlassen dieser Seite kann dazu führen, dass deine Änderungen verloren gehen.\nWenn du angemeldet bist, kannst du das Anzeigen dieser Warnung im Bereich „{{int:prefs-editing}}“ deiner Einstellungen abschalten.",
+ "editpage-invalidcontentmodel-title": "Das Inhaltsmodell wird nicht unterstützt.",
+ "editpage-invalidcontentmodel-text": "Das Inhaltsmodell „$1“ wird nicht unterstützt.",
"editpage-notsupportedcontentformat-title": "Das Inhaltsformat wird nicht unterstützt",
"editpage-notsupportedcontentformat-text": "Das Inhaltsformat $1 wird vom Inhaltsmodell $2 nicht unterstützt.",
"content-model-wikitext": "Wikitext",
"pageinfo-article-id": "Seitenkennnummer",
"pageinfo-language": "Seiteninhaltssprache",
"pageinfo-content-model": "Seiteninhaltsmodell",
+ "pageinfo-content-model-change": "ändern",
"pageinfo-robot-policy": "Indizierung durch Suchmaschinen",
"pageinfo-robot-index": "Erlaubt",
"pageinfo-robot-noindex": "Nicht erlaubt",
"tag-filter": "[[Special:Tags|Markierungs]]-Filter:",
"tag-filter-submit": "Filter",
"tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Markierung|Markierungen}}]]: $2)",
+ "tag-mw-contentmodelchange": "Änderung des Inhaltsmodells",
+ "tag-mw-contentmodelchange-description": "Bearbeitungen, die [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel das Inhaltsmodell einer Seite ändern]",
"tags-title": "Markierungen",
"tags-intro": "Diese Seite zeigt alle Markierungen, die für Bearbeitungen verwendet wurden, sowie deren Bedeutung. \n\nBei entsprechender Einstellung können die Missbrauchfilter beliebige Markierungen in die Versionsgeschichte setzen. Man kann die Versionsgeschichte dann nach den Markierungen filtern.",
"tags-tag": "Markierungsname",
"tags-actions-header": "Aktionen",
"tags-active-yes": "Ja",
"tags-active-no": "Nein",
- "tags-source-extension": "Definiert von einer Erweiterung",
+ "tags-source-extension": "Definiert von der Software",
"tags-source-manual": "Manuell von Benutzern und Bots eingesetzt",
"tags-source-none": "Nicht mehr in Verwendung",
"tags-edit": "bearbeiten",
"editfont-monospace": "Terzê nusteyê sabıtcagırewtoği",
"editfont-sansserif": "Fontê Sans-serifi",
"editfont-serif": "Font (çêşıdê nuştey) Serif",
- "sunday": "Kırê (Bazar)",
+ "sunday": "Kırê",
"monday": "Dışeme",
"tuesday": "Sêşeme",
"wednesday": "Çarşeme",
"oct": "Tşv",
"nov": "Tşp",
"dec": "Kan",
- "january-date": "Çele $1",
- "february-date": "Sıbate $1",
- "march-date": "Adar $1",
- "april-date": "Nisane $1",
- "may-date": "Gulane $1",
- "june-date": "{{PLURAL:$1|1=1ᵉ|$1}} Heziran",
- "july-date": "Temuz $1",
- "august-date": "Tebaxe $1",
- "september-date": "Keşkelun $1",
- "october-date": "Tışrino Verên $1",
- "november-date": "Tışrino Peyên $1",
- "december-date": "Kanun $1",
+ "january-date": "$1 Çele",
+ "february-date": "$1 Sıbate",
+ "march-date": "$1 Adar",
+ "april-date": "$1 Nisane",
+ "may-date": "$1 Gulane",
+ "june-date": "$1 Heziran",
+ "july-date": "$1 Temuze",
+ "august-date": "$1 Tebaxe",
+ "september-date": "$1 Keşkelun",
+ "october-date": "$1 Tışrino Verên",
+ "november-date": "$1 Tışrino Peyên",
+ "december-date": "$1 Kanun",
"period-am": "AM",
"period-pm": "PM",
"pagecategories": "{{PLURAL:$1|Kategori|Kategoriy}}",
"category-empty": "''Ena kategoriye de hewna qet nuştey ya zi medya çıniyê.''",
"hidden-categories": "{{PLURAL:$1|Kategoriya nımıtiye|Kategoriyê nımıtey}}",
"hidden-category-category": "Kategoriyê nımıtey",
- "category-subcat-count": "{{PLURAL:$2|Na kategoriya de $1 bınkategoriyay estê.|$2 kategoriyan ra $1 bınkategoriyay asenê.}} \n(K) Kategori (D) Dosya (P) Pela",
+ "category-subcat-count": "{{PLURAL:$2|Na kategoriye de $1 bınkategoriy estê.|$2 kategoriyan ra $1 kategoriyê bınêni asenê.}} \n{| border=\"1\" cellpadding=\"2\" cellspacing=\"0\" align=\"left\" style=\"margin-left:1em; background:peru; border: 1px #aaa solid; border-collapse: collapse; font-size: 95%;\"\n| align=\"center\" |(K) Kategoriye (D) Dosya (P) Peli (M) Medya\n|}",
"category-subcat-count-limited": "Na kategoriye de {{PLURAL:$1|na kategoriya bınêne esta|nê $1 kategoriyê bınêni estê}}.",
"category-article-count": "{{PLURAL:$2|Na kategoriye de teyna ena pele esta.|Ebe $2 ra pêro piya {{PLURAL:$1|ena pela na kategoriye dera|$1 enê peli na kategoriye derê.}}}}",
"category-article-count-limited": "{{PLURAL:$1|Pela cêrêne|$1 Pelê cêrêni}} na kategoriye derê.",
"specialpage": "Pela xısusiye",
"personaltools": "Hacetê şexsiy",
"articlepage": "Pera zerreki bıvin",
- "talk": "Vaten",
+ "talk": "Werênayış",
"views": "Asayışi",
"toolbox": "Haceti",
"userpage": "Pela karberi bıvêne",
"viewsourcelink": "çımey bıvêne",
"editsectionhint": "Leteyo ke bıvuriyo: $1",
"toc": "Sernameyê meselan",
- "showtoc": "bıasene",
+ "showtoc": "bımocne",
"hidetoc": "bınımne",
- "collapsible-collapse": "Teng ke",
+ "collapsible-collapse": "Teng kı",
"collapsible-expand": "Hera ke",
- "confirmable-confirm": "{{GENDER:$1|Şıma }} do emeli?",
+ "confirmable-confirm": "{{GENDER:$1|Şıma}} pêbawerê?",
"confirmable-yes": "Eya",
"confirmable-no": "Nê",
"thisisdeleted": "Bıvêne ya zi $1 peyser biya?",
"cannotlogoutnow-title": "Enewke ronıştışo nêracneyêno",
"welcomeuser": "Ğeyr amey, $1!",
"welcomecreation-msg": "Hesabê şıma abiyo.\n[[Special:Preferences|{{SITENAME}} vurnayişê tercihanê xo]], xo vir ra mekere.",
- "yourname": "Nameyê karberi:",
- "userlogin-yourname": "Nameyê karberi",
+ "yourname": "Namey karberi:",
+ "userlogin-yourname": "Namey karberi",
"userlogin-yourname-ph": "Nameyê xoyê karberi cı kewe",
"createacct-another-username-ph": "Nameyê karberi cı kewe",
"yourpassword": "Parola",
"userlogin-signwithsecure": "Ebe teqdimkerê asayişın cıkewe",
"cannotloginnow-title": "Enewke ronıştışo nêabeno",
"cannotloginnow-text": "$1 karkerdışa ronıştış akerdış mıkum niyo.",
- "yourdomainname": "Nameyê şıma yo meydani",
+ "yourdomainname": "Yewdestê şıma:",
"password-change-forbidden": "Şıma na wiki de nêşenê parola bıvurnê.",
"externaldberror": "Ya database de xeta esta ya zi heqê şıma çino şıma no hesab bıvurni.",
"login": "Cı kewe",
"rev-deleted-diff-view": "Jew timarkerdışê ena versiyon '''wedariyayo''.\nÎdarekarî şenê ena versiyon bivîne; belki tiya de [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} wedarnayişî] de teferruat esto.",
"rev-suppressed-diff-view": "Jew timarkerdışê ena versiyon '''Ploxneyış'' biyo.\nÎdarekarî eşkeno ena dif bivîne; belki tiya de [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} ploxnayış] de teferruat esto.",
"rev-delundel": "bımocne/bınımne",
- "rev-showdeleted": "bıasene",
+ "rev-showdeleted": "bımocne",
"revisiondelete": "Çımraviyarnayışan bestere/peyser biya",
"revdelete-nooldid-title": "Çımraviyarnayışo waşte nêvêreno",
"revdelete-nooldid-text": "Şıma vıraştışê nê fonksiyoni rê ya yew çımraviyarnayışo waşte diyar nêkerdo, çımraviyarnayışo diyarkerde çıniyo, ya ki şıma wazenê ke çımraviyarnayışê nıkayêni bınımnê.",
"rcnotefrom": "Cêr de <strong>$2</strong> ra nata {{PLURAL:$5|vurnayışiyê}} asenê (tewr vêşi <strong>$1</strong> asenê) <strong>$3, $4</strong>",
"rclistfrom": "$3 $2 ra tepiya vurnayışanê neweyan bımocne",
"rcshowhideminor": "vurriyayışê werdi $1",
- "rcshowhideminor-show": "Bımocne",
+ "rcshowhideminor-show": "Bımusn",
"rcshowhideminor-hide": "Bınımne",
"rcshowhidebots": "botan $1",
"rcshowhidebots-show": "Bımocne",
"withoutinterwiki": "Pelê ke zıwananê binan rê gıreyê cı çıniyo",
"withoutinterwiki-summary": "Enê pelî ke versiyonê ziwanî binî ra link nidano.",
"withoutinterwiki-legend": "Verole",
- "withoutinterwiki-submit": "Bıasene",
+ "withoutinterwiki-submit": "Bımocne",
"fewestrevisions": "Pelê be senık çımraviyarnayışi",
"nbytes": "$1 {{PLURAL:$1|bayt|bayti}}",
"ncategories": "$1 {{PLURAL:$1|Kategori|Kategoriy}}",
"mostrevisions": "Pelan ke tewr zaf revizyonî biyê.",
"prefixindex": "Veroleya peley pêro",
"prefixindex-namespace": "Peleyê Veroleyıni ($1 cay nami)",
- "prefixindex-submit": "Bıasene",
+ "prefixindex-submit": "Bımocne",
"prefixindex-strip": "Listeya réz bıyayışi",
"shortpages": "Pelê kılmeki",
"longpages": "Pelê dergeki",
"usereditcount": "$1 {{PLURAL:$1|vurnayîş|vurnayîşî}}",
"usercreated": "$2 de $1 {{GENDER:$3|viraziya}}",
"newpages": "Pelê newey",
- "newpages-submit": "Bıasene",
+ "newpages-submit": "Bımocne",
"newpages-username": "Nameyê karberi:",
"ancientpages": "Pelê kehenêri",
"move": "Bıkırışe",
"specialloguserlabel": "Kerdoğ:",
"speciallogtitlelabel": "Meqsed (sername ya zi {{ns:user}}:karberi rê nameyê karberi):",
"log": "Qeydi",
- "logeventslist-submit": "Bıasene",
+ "logeventslist-submit": "Bımocne",
"all-logs-page": "Umumi qeydi pêro",
"alllogstext": "qey {{SITENAME}}i mocnayişê heme rocaneyani.\ntipa rocaneyi, nameyê karberi (herfa pil u qıci re hessas a), ya zi peli (reyna hessasiyê herfa pil u qıciyi) bıweçine u esayiş qıc kerê.",
"logempty": "Qeydan dı malumato unasin çıni yo.",
"cachedspecial-viewing-cached-ts": "Na pela raşt niya, şımayê enewke versiyonê verhafızada na pela vinenê.",
"cachedspecial-refresh-now": "Peyêni bıvin.",
"categories": "Kategoriy",
- "categories-submit": "Bıasene",
+ "categories-submit": "Bımocne",
"categoriespagetext": "{{PLURAL:$1|Kategoriya cêrene|Kategoriyanê cêrênan}} de peli ya zi medya estê.\n[[Special:UnusedCategories|Kategoriyê ke nêxebetiyenê]] tiya de nêmocniyayê.\n[[Special:WantedCategories|Kategoriyanê waşteyeyan]] de zi bıvêne.",
"categoriesfrom": "Kategoriyê ke be ninan dest pêkenê, bımocne:",
"deletedcontributions": "İştırakê karberi esterdi",
"linksearch-line": "$1, $2 ra link biya",
"linksearch-error": "jokeri têna nameyê makina ya serekini de aseni/eseni.",
"listusersfrom": "karber ê ke pey ıney detpêkeni ramocın:",
- "listusers-submit": "Bıasene",
+ "listusers-submit": "Bımocne",
"listusers-noresult": "karber nêdiyayo/a.",
"listusers-blocked": "(blok biy)",
"activeusers": "Listey karberan de aktivan",
"wlnote": "$3 saete $4 ra dıme {{PLURAL:$2|yew saete de|'''$2''' saetan de}} {{PLURAL:$1|vurnayışo peyên|vurnayışê '''$1''' peyêni}} cêrderê.",
"wlshowlast": "Peyni de $1 seata u $2 roca bıasne",
"watchlist-hide": "Bınımne",
- "watchlist-submit": "Bıasene",
+ "watchlist-submit": "Bımocne",
"wlshowtime": "Periyoda zemani asenayışi:",
"wlshowhideminor": "vurriyayışê werdi",
"wlshowhidebots": "boti",
"delete-confirm": "\"$1\" bestere",
"delete-legend": "Bestere",
"historywarning": "'''Teme:''' Pela ke şıma esterenê tede yew viyarte be teqriben $1 {{PLURAL:$1|versiyon esto|versiyoni estê}}:",
- "historyaction-submit": "Bıasene",
+ "historyaction-submit": "Bımocne",
"confirmdeletetext": "Tı ho yew pele u tarixê pele wederneno.\nTı ra rica keno, tı zani tı ho sekeno, tı zani neticeyanê eno wedarnayışi u tı zani tı ser [[{{MediaWiki:Policy-url}}|poliçe]] kar keno.",
"actioncomplete": "Kar bi temam",
"actionfailed": "kar nêbı",
"tooltip-pt-login": "Mayê şıma ronıştış akerdışi rê dawet keme; labelê ronıştış mecburi niyo",
"tooltip-pt-logout": "Bıveciye",
"tooltip-pt-createaccount": "Şıma rê tewsiyey ma xorê jew hesab akerê. Fına zi hesab akerdış mecburi niyo.",
- "tooltip-ca-talk": "Zerrekê pele sero werênayış",
+ "tooltip-ca-talk": "Heqa zerrekê pele de werênayış",
"tooltip-ca-edit": "Ena pele bıvurne",
"tooltip-ca-addsection": "Zu bınnusteya newi ak",
"tooltip-ca-viewsource": "Ena pele kılit biya.\nŞıma şenê çımeyê aye bıvênê",
"monday": "सौउबार",
"tuesday": "माङलबार",
"wednesday": "बुधबार",
- "thursday": "बिपैबार",
+ "thursday": "बà¥\80पैबार",
"friday": "शुकबार",
"saturday": "छन्चरबार",
"sun": "आइत",
"mon": "सौउ",
"tue": "मांगल",
- "wed": "बà¥\81ध",
- "thu": "बिपै",
+ "wed": "बà¥\8bऽ",
+ "thu": "बà¥\80पै",
"fri": "शुक",
"sat": "छन्चर",
"january": "जनवरी",
"period-am": "रात १२ बज्या बठे छाकला सम्म",
"period-pm": "छाकला बठे रात १२ बज्या सम्म",
"pagecategories": "{{PLURAL:$1|श्रेणी|श्रेणीन}}",
- "category_header": "\"$1\" श्रेणीमी भया लेखहरू",
+ "category_header": "\"$1\" श्रेणीमी भयाऽ लेखअन",
"subcategories": "उपश्रेणीहरू",
"category-media-header": "\"$1\" श्रेणीमी भया लेखहरू",
"category-empty": "''यै श्रेणीमी हाल कोइलै पाना या मिडिया रया नाइँथिन ।''",
"mypage": "पानो",
"mytalk": "मेरी कुरडी",
"anontalk": "कुरडी",
- "navigation": "à¤\96à¥\8bà¤\9c",
+ "navigation": "पथपà¥\8dरदरà¥\8dशन",
"and": " रे",
"qbfind": "तम जाण",
"qbbrowse": "ब्राउज गर्न्या",
"categorypage": "श्रेणी पानो हेर",
"viewtalkpage": "छलफल हेर",
"otherlanguages": "और भषाअनमी",
- "redirectedfrom": "($1 बाà¤\9f पठाà¤\87याà¤\95à¥\8b)",
+ "redirectedfrom": "($1 बठà¥\87à¤\87 पà¥\81न:निरà¥\8dदà¥\87शित)",
"redirectpagesub": "अनुप्रेषित पानो",
"redirectto": "पठाएको पाना:",
"lastmodifiedat": "यै पन्नालाई छाड्डीबाऱ $2 बजे, $1 मी हेरफेर गरियाऽ थ्यो।",
"aboutsite": "{{SITENAME}}आ बारेमी",
"aboutpage": "Project:बारेमी",
"copyright": "सामाग्री $1 अनुसार उपलब्ध छ, खुलाइएको अवस्था बाहेकका हकमी ।",
- "copyrightpage": "{{ns:project}}:पà¥\8dरतिलिपà¥\80 à¤\85धिà¤\95ारहरà¥\82",
- "currentevents": "आजभोलका घटनाहरू",
- "currentevents-url": "Project:आजभोलका घटनाहरू",
+ "copyrightpage": "{{ns:project}}:पà¥\8dरतिलिपà¥\80 à¤\85धिà¤\95ारà¤\85न",
+ "currentevents": "आजभोलका घटना",
+ "currentevents-url": "Project:आजभोलका घटना",
"disclaimers": "अस्विकारोक्तीन",
"disclaimerpage": "Project:सामान्य अस्वीकारोक्ति",
"edithelp": "सम्पादन सहायता",
"ok": "भयो",
"retrievedfrom": " \"$1\" बठे निकालियाऽ",
"youhavenewmessages": "{{PLURAL:$3|तम सित छन}} $1 ($2)।",
- "youhavenewmessagesfromusers": "तमखी लेखा {{PLURAL:$3|प्रयोगकर्ता|$3 प्रयोगकर्तान}}($2)बठे$1",
+ "youhavenewmessagesfromusers": "{{PLURAL:$3|अर्खा प्रयोगकर्ता|$3 प्रयोगकर्ताअन}} ($2) मी है {{PLURAL:$4|तम सित}} $1 छन।",
"youhavenewmessagesmanyusers": "तमलाई धेरै प्रयोगकर्ताहरू($2) बठे $1 छ ।",
"newmessageslinkplural": "{{PLURAL:$1|एक नौलो रैबार|999=नौला रैबारहरू}}",
"newmessagesdifflinkplural": "छाड्डीबारको {{PLURAL:$1|परिवर्तन|999=परिवर्तनहरू}}",
"laggedslavemode": "<strong>चेतावनी:</strong> पानामी हालका अद्यतनहरू नहुनस्कदान ।",
"readonly": "डेटाबेस बन्द गरिया छ",
"enterlockreason": "ताल्चा मार्नुको कारण दिया, साथै ताल्चा हटाउने समयको अवधि अनुमान लगा।",
- "readonlytext": "समà¥\8dà¤à¤µà¤¤à¤\83 नियमित डà¥\87à¤\9fाबà¥\87स रà¤\96-रà¤\96ाà¤\89à¤\95à¥\8b à¤\95ारण à¤\85हिलà¥\87लाà¤\88 नयाà¤\81 डà¥\87à¤\9fाबà¥\87स पà¥\8dरविषà¥\8dà¤\9fà¥\80 र à¤\85नà¥\8dय सà¤\82शà¥\8bधनहरà¥\82 बनà¥\8dद राà¤\96िया à¤\9b, à¤\9cà¤\88लाà¤\88 पà¤\9bि बठà¥\87 सामानà¥\8dय à¤\97रिनà¥\8dया à¤\9b। \nपà¥\8dरबनà¥\8dधà¤\95 à¤\9cà¤\88लà¥\87 यà¥\8b बनà¥\8dद à¤\97रà¥\8dयाà¤\9bनà¥\8d, यà¥\8b सà¥\8dपषà¥\8dà¤\9fà¥\80à¤\95रण दियाà¤\95ाà¤\9bनà¥\8d: $1",
+ "readonlytext": "नà¥\8cला पà¥\8dरविषà¥\8dà¤\9fà¥\80 रà¥\87 à¤\94र सà¤\82शà¥\8bधनà¤\85न à¤\96िलाà¤\88 डाà¤\9fाबà¥\87स à¤\85à¤\87ल बनà¥\8dद à¤\85रà¥\80रà¥\88à¤\9b़, समà¥\8dà¤à¤¬à¤¤: नियमित डाà¤\9fाबà¥\87स रà¤\96-रà¤\96ाà¤\89 à¤\96िलाà¤\87, à¤\9cà¥\88 पà¤\9bा यà¥\8b सामानà¥\8dय à¤\85वसà¥\8dथा मà¥\80 à¤\86सलà¥\8b। \n\nवà¥\8dयवसà¥\8dथापà¤\95 à¤\9cà¤\88लà¥\87 यà¥\8b बनà¥\8dद à¤\85रिराà¤\87à¤\9b à¤\89नलà¥\87 यà¥\87à¤\87 सà¥\8dपषà¥\8dà¤\9fà¥\80à¤\95रण दà¥\80राà¤\87à¤\9b़: $1",
"missing-article": "नाम \"$1\" $2 भयाको भेटिनु पड्डे पानो पाठ डेटाबेसले भेटाएन, \n\nयिसो प्राय: मिति नाघिसक्याको भिन्न वा इतिहास वा कुनै मेटिसक्याको पानाको लिंक पहिल्याउनाले हुन्छ ।\n\nयदि यसो भया नाइँहो भणे सफ्टवेयरको गल्ती लै हुनसकुन्छ ।\nकृपया यैको url खुलाइ [[Special:ListUsers/sysop|प्रबन्धक]]लाई उजुरी गर",
"missingarticle-rev": "(संशोधन #: $1)",
"missingarticle-diff": "(भिन्नता: $1, $2)",
"createacct-yourpassword-ph": "पासवर्ड लेख",
"yourpasswordagain": "पासवर्ड फेरि टाईप गर",
"createacct-yourpasswordagain": "पासवर्ड निश्चित गर",
- "createacct-yourpasswordagain-ph": "à¤\86à¤\9cà¥\80 पासवरà¥\8dड लà¥\87à¤\96",
+ "createacct-yourpasswordagain-ph": "à¤\86à¤\81à¤\9cि पासवरà¥\8dड à¤à¤°à¤½",
"userlogin-remembermypassword": "मुलाई अघाडी झान्या काम गराइराख्या",
"userlogin-signwithsecure": "सुक्षित जडान प्रयोग गद्द्या",
+ "cannotlogin-title": "अईल भितर झान नाइँ पाईनो",
+ "cannotlogin-text": "येइमी लगइन सम्भव नाइथिन।",
"cannotloginnow-title": "अईल भितर झान नाइँ पाईनो",
"cannotloginnow-text": "भितर जान असंभव छ जब प्रयोग $1|",
+ "cannotcreateaccount-title": "खाता बनौन नाइसक्दो",
+ "cannotcreateaccount-text": "प्रत्यक्ष खाता बनौन एइ विकि मी सक्षम अरीयाऽ आथिन।",
"yourdomainname": "तमरो ज्ञानक्षेत्र(डोमेन):",
"password-change-forbidden": "ये विकिमी पासवर्ड परिवर्तन गर्न सक्नुहुन्न।",
+ "externaldberror": "या त याँ प्रमाणीकरण डाटाबेस त्रुटी थी या त तमलाई अफुना बाइल्ला खातालाई अद्यतन अद्देइ अनुमति आथिन।",
"login": "प्रवेश (लगईन)",
"login-security": "तमरो पहिचान जाचँ गर",
"nav-login-createaccount": "प्रवेश गर्ने/नयाँ खाता बनाउन्या",
"userlogin-createanother": "दोसरो खाता खोल",
"createacct-emailrequired": "इमेल ठेगाना",
"createacct-emailoptional": "इमेल ठेगाना (ऐच्छिक)",
- "createacct-email-ph": "तमरà¥\8b à¤\87मà¥\87ल ठà¥\87à¤\97ाना à¤à¤°à¤¯à¤¾",
+ "createacct-email-ph": "तमरà¥\8b à¤\87मà¥\87ल ठà¥\87à¤\97ाना à¤à¤°à¤½",
"createacct-another-email-ph": "इमेल ठेगाना भर",
"createacct-realname": "वास्तविक नाम (ऐच्छिक)",
"createaccountreason": "कारण:",
"createacct-reason": "कारण",
"createacct-reason-ph": "क्याई तम नयाँ खाता खोल्ला छौ?",
- "createacct-submit": "तमरà¥\8b à¤\96ाता सिरà¥\8dà¤\9cना à¤\97र",
+ "createacct-submit": "तमरà¥\8b à¤\96ाता बनाऽ",
"createacct-another-submit": "खाता खोल",
"createacct-continue-submit": "खाता खोल्लु जारि राख",
"createacct-another-continue-submit": "खाता खोल्लु जारि राख",
"passwordreset-emailsentemail": "यदि यो इमेल ठेगाना तम सित सम्बन्धित छ भण्या, तब यक पासवर्ड रिसेट इमेल पठाएलो।",
"passwordreset-invalideamil": "अबैध ई-मेल ठेगाना",
"changeemail": "इमेल ठेगाना बदेल वा हटा",
- "changeemail-header": "à¤\86फà¥\8dनà¥\8b à¤\87मà¥\87ल ठà¥\87à¤\97ाना परिवरà¥\8dतन à¤\97दà¥\8dद यà¥\8b फारम à¤à¤° । यà¥\88लाà¤\88 पà¥\81षà¥\8dà¤\9fि à¤\97दà¥\8dद तमà¥\80लà¥\87 à¤\86फà¥\8dनà¥\8b पासवरà¥\8dड हालà¥\8dनà¥\81 पडनà¥\8dà¤\9b।",
+ "changeemail-header": "तमरà¥\8b à¤\87मà¥\87ल ठà¥\87à¤\97ाना बदà¥\87लà¥\8dलाà¤\87 à¤\8fà¤\87 फाराम पà¥\81राà¤\87 à¤à¤°à¤½à¥¤ यदि तम तमरा à¤\96ाता बठà¥\87à¤\87 à¤\95सà¥\88 लà¥\88 à¤\87मà¥\87ल ठà¥\87à¤\97ाना सितà¥\8bऽ समà¥\8dबनà¥\8dध हà¤\9fà¥\8cन à¤\9aाहनà¥\8dà¤\9bऽ à¤à¤£à¥\8dया, फाराम बà¥\81à¤\9cà¥\8cनà¥\8dà¤\9cà¥\8dयाà¤\81 नà¥\8cलà¥\8b à¤\87मà¥\87ल ठà¥\87à¤\97ाना à¤à¤£à¥\8dणà¥\8dया ठà¥\8cर à¤\96ालि à¤\9bाणà¥\8dयाऽ।",
"changeemail-oldemail": "अईलको इमेल-ठेगाना:",
"changeemail-newemail": "नयाँ इमेल-ठेगाना:",
"changeemail-none": "(के लै नाइँ)",
"note": "'''सूचना:'''",
"previewnote": "<strong>फाम अर: कि यो यक पूर्वावलोकन मात्तरी हो।</strong>\nतमले अर्या फेरबदेली आँजि सङ्ग्रहित भया: आथिन!",
"continue-editing": "सम्पादन क्षेत्रमी जाओ",
- "editing": "$1 समà¥\8dपादन à¤\97रिदà¥\88",
+ "editing": "$1 समà¥\8dपादन à¤\85रà¥\80नà¥\8dनाà¤\9b़",
"creating": "$1 बनाइँदै",
"editingsection": "$1 (खण्ड) सम्पादन गरिदै",
"editingcomment": "$1 सम्पादन गर्दै(नयाँ खण्ड)",
"revdelete-unsuppress": "पुनर्स्थापित पुनरावृत्तिबठे बन्देज हटाउन्या",
"revdelete-log": "कारण:",
"revdelete-submit": "{{PLURAL:$1|छानिया संशोधन|छान्निया संशोधनहरू}}मी प्रयोग गर्न्या",
- "revdelete-success": "'''संशोधन दृश्यता सफलतापूर्वक अद्यतन भयो।'''",
+ "revdelete-success": "संशोधन दृश्यता अद्यतन अरियो।",
"revdelete-failure": "'''संशोधन दृश्यता अद्यतन गर्न सकिएन:'''\n$1",
"logdelete-success": "लग दृष्टि मिलाइयो ।",
"logdelete-failure": "'''लग दृष्टि मिलाउन सकिएन :'''\n$1",
"nextn-title": "यै पछाका $1 {{PLURAL:$1|नतिजा |नतिजाहरू}}",
"shown-title": "धेखाउने $1 {{PLURAL:$1|नतिजा|नतिजाहरू}} प्रति पाना",
"viewprevnext": "हेर ($1 {{int:pipe-separator}} $2) ($3)",
- "searchmenu-exists": "''' \"[[:$1]]\" नाम गरया पाना ये विकीमी रह्या छ'''",
+ "searchmenu-exists": "<strong>\"[[:$1]]\" नाउँ अरियाऽ पन्ना ये विकीमी छ।</strong>{{PLURAL:$2|0=| पाइयाऽ और खोजी नतिजाअन लै तकऽ।}}",
"searchmenu-new": "<strong>\"[[:$1]]\" पानो इसै विकिमी बनाओ !</strong> {{PLURAL:$2|0=|तमले खोज अरी भेटियाको पानो पन सङ्ङै जोड्या काम अर ।|तमरो खोज परिणाम पन हेर।}}",
- "searchprofile-articles": "सामà¤\97à¥\8dरà¥\80 पानाहरà¥\82",
+ "searchprofile-articles": "सामà¤\97à¥\8dरà¥\80 पनà¥\8dनाà¤\85न",
"searchprofile-images": "मल्टिमिडिया(श्रव्य दृश्य)",
"searchprofile-everything": "सबै थोक",
"searchprofile-advanced": "उन्नत",
"searchprofile-articles-tooltip": "$1 मी खोज्या",
- "searchprofile-images-tooltip": "फाइलहरू खोज्ज्या",
+ "searchprofile-images-tooltip": "फाइल कि लेखा खोजऽ",
"searchprofile-everything-tooltip": "सबै सामग्री खोज्या (वार्तालाप लै )",
"searchprofile-advanced-tooltip": "अनुकुल नेमस्पेसमा खोज्या",
- "search-result-size": "$1 ({{PLURAL:$2|1 शबà¥\8dद|$2 शबà¥\8dदहरà¥\82}})",
+ "search-result-size": "$1 ({{PLURAL:$2|1 à¤\86à¤\81à¤\96र|$2 à¤\86à¤\81à¤\96र}})",
"search-result-category-size": "{{PLURAL:$1|एक सदस्य|$1 सदस्यहरू}} ({{PLURAL:$2|1 उपश्रेणी|$2 उपश्रेणीहरू}}, {{PLURAL:$3|एउटा फाइल|$3 फाइलहरू}})",
"search-redirect": "(जान्या $1)",
"search-section": "(खण्ड $1)",
"search-interwiki-more": "(आजी)",
"search-relatedarticle": "सम्बन्धित",
"searchrelated": "सम्बन्धित",
- "searchall": "सबै",
+ "searchall": "सपà¥\8dपै",
"showingresults": "धेखाउँदै {{PLURAL:$1|'''१''' नतिजा|'''$1''' नतिजाहरू }} , #'''$2''' बठे सुरुहुन्या ।",
"showingresultsinrange": "देखाई रह्या छ{{PLURAL:$1|<strong>1</strong> result|<strong>$1</strong> परिणाम}} सम्म पहुँच #<strong>$2</strong> देखि #<strong>$3</strong> मी।",
"search-showingresults": "{{PLURAL:$4|<strong>$3</strong> मै बठे <strong>$1</strong> परिणाम|<strong>$3</strong> मै बठे परिणाम <strong>$1 - $2</strong>}}",
"prefs-editwatchlist-raw": "कच्चा अवलोकनसूची सम्पादन गद्दा",
"prefs-editwatchlist-clear": "तमरो अवलोकनसूची मेटा",
"prefs-watchlist-days": "ध्यान सूचीमी धेकाउने दिनहरू:",
- "prefs-watchlist-days-max": "à¤à¥\8cत $1 {{PLURAL:$1|दिन|दिन}}",
+ "prefs-watchlist-days-max": "बरà¥\8dतà¥\80 हà¥\88 बरà¥\8dतà¥\80 $1 {{PLURAL:$1|दिन|दिनà¤\85न}}",
"prefs-watchlist-edits": "उच्चतम परिवर्तन संख्या बढाइएको निगरानी सूचीमी धकाउनका लागि :",
"prefs-watchlist-edits-max": "सबै है ज्यादा संख्या : १०००",
"prefs-watchlist-token": "अवलोकन सूची टोकन:",
"stub-threshold-sample-link": "उदाहरण",
"stub-threshold-disabled": "निष्क्रिय",
"recentchangesdays": "हालको परिवर्तनमी धेकाउने दिनहरू:",
- "recentchangesdays-max": "à¤\85धिà¤\95तम $1 {{PLURAL:$1|दिन|दिन}}",
+ "recentchangesdays-max": "à¤\9cà¥\87दा हà¥\88 à¤\9cà¥\87दा $1 {{PLURAL:$1|दिन|दिनà¤\85न}}",
"timezonelegend": "समय क्षेत्र :",
"localtime": "स्थानिय समय:",
"timezoneuseserverdefault": "विकि मूल ($1) रुपमी प्रयोग गर्ने",
"userrights-groupsmember": "को सदस्य:",
"userrights-groupsmember-auto": "अंतर्निहित सदस्य:",
"userrights-reason": "कारण:",
+ "userrights-changeable-col": "तमले परिवर्तन गद्द सक्दया समूहअन",
"userrights-unchangeable-col": "तमीले परिवर्तन गद्द नसक्ने समूहहरू",
"userrights-conflict": "प्रयोगकर्ताको अधिकार परिवर्तनमी मतभेद भयो ! कृपया तमरो परिवर्तन पुनरावलोकन तथा पुष्टि गर ।",
"userrights-removed-self": "तमले सफलतापूर्वक आफनो अधिकारहरूलाई मेटाया । त्यै कारण तम आब यो पानो हेद्द नाइसक्दा ।",
"group-bureaucrat-member": "{{GENDER:$1|प्रशासक}}",
"group-suppress-member": "{{GENDER:$1|दबाउन्या}}",
"grouppage-user": "{{ns:project}}:प्रयोगकर्ताहरू",
- "grouppage-autoconfirmed": "{{एनयस:आयोजना}}:स्वनिर्धारित प्रयोगकर्ताहरू",
- "grouppage-bot": "{{एनयस:आयोजना}}:बोटहरु",
+ "grouppage-autoconfirmed": "{{ns:project}}:स्वतःपुष्टि भयाऽ प्रयोगकर्ताअन",
+ "grouppage-bot": "{{ns:project}}:बोटअन",
"grouppage-sysop": "{{ns:project}}:प्रबन्धकहरू",
- "grouppage-bureaucrat": "{{एनयस:आयोजना}}:प्रशासकहरू",
- "grouppage-suppress": "{{एनयस:आयोजना}}:लुकौन्या",
+ "grouppage-bureaucrat": "{{ns:project}}:प्रशासकअन",
+ "grouppage-suppress": "{{ns:project}}:लुकौन्या",
"right-read": "पृष्ठहरू पढ",
"right-edit": "पृष्ठहरू सम्पादन गर",
"right-createpage": "पृष्ठ निर्माण गर(छलफल पृष्ठहरू बाहेक)",
"right-userrights-interwiki": "अन्य विकिहरूमी प्रयोगकर्ताहरूको अधिकार सम्पादन गद्या",
"right-override-export-depth": "गहिराइ ५ सम्म लिंक गरियाका पानाहरू सहित निर्यात गद्या",
"right-sendemail": "अन्य प्रयोगकर्तानलाई इमेल पठाउन्या",
- "grant-editmycssjs": "तमरो प्रयोगकर्ता CSS/JavaScript सम्पादन गर",
- "grant-editmyoptions": "तमरा पà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता à¤\85à¤à¤¿à¤°à¥\82à¤\9aà¥\80हरà¥\82लाà¤\88 समà¥\8dपादन à¤\97र",
+ "grant-editmycssjs": "तमरो प्रयोगकर्ता CSS/JavaScript सम्पादन गरऽ",
+ "grant-editmyoptions": "तमरा पà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता à¤\85à¤à¤¿à¤°à¥\81à¤\9aà¥\80à¤\87नलाà¤\88 समà¥\8dपादन à¤\97रऽ",
"grant-editmywatchlist": "तमरो अवलोकनसूची सम्पादन गर",
"grant-editpage": "भैरया पृष्ठहरू सम्पादन गर",
"grant-editprotected": "सुरक्षित पृष्ठ सम्पादन",
"recentchanges-legend": "अच्यालैका परिवर्तन विकल्पहरू",
"recentchanges-summary": "विकिका यैल्लैका फेरबदललाई यै पानामि पहिल्याउन्या",
"recentchanges-label-newpage": "यै सम्पादनले नौलो पानो बनायाको छ",
- "recentchanges-label-minor": "यो नानो सम्पादन हो",
+ "recentchanges-label-minor": "यà¥\8b नाऽनà¥\8b समà¥\8dपादन हà¥\8b",
"recentchanges-label-bot": "यो सम्पादन बोटबठे गरियाको थ्यो",
"recentchanges-label-unpatrolled": "यो सम्पादन यैलसम्म गस्ती गरियाको नाइथी",
"recentchanges-label-plusminus": "यति बाइटहरू संख्याले पानाको आकार फेरबदल भयाको छ",
"rclistfrom": "$3 $2 देखिका नयाँ परिवर्तनहरू देखाउन्या",
"rcshowhideminor": "$1 सानतिनो सम्पादन",
"rcshowhideminor-show": "धेकाइदिय",
- "rcshowhideminor-hide": "लà¥\81à¤\95ाà¤\89नà¥\8dया",
+ "rcshowhideminor-hide": "लà¥\81à¤\95ाऽ",
"rcshowhidebots": "$1 बोटहरू",
"rcshowhidebots-show": "धेकाइदिय",
"rcshowhidebots-hide": "लुकाइदिय",
"rcshowhideliu": "$1 दर्ता अर्याका प्रयोगकर्ताहरू",
"rcshowhideliu-hide": "लुकाउन्या",
"rcshowhideanons": "$1 नपछेण्याका प्रयोगकर्ता",
- "rcshowhideanons-show": "धà¥\87à¤\95ाà¤\87दिय",
- "rcshowhideanons-hide": "लà¥\81à¤\95ाà¤\89नà¥\8dया",
+ "rcshowhideanons-show": "धà¥\87à¤\95ाऽ",
+ "rcshowhideanons-hide": "लà¥\81à¤\95ाऽ",
"rcshowhidepatr": "$1 पट्रोल गर्याका सम्पादनहरू",
"rcshowhidemine": "$1 मेरा सम्पादनहरू",
"rcshowhidemine-show": "धेकाइदिय",
- "rcshowhidemine-hide": "लà¥\81à¤\95ाà¤\87दिय",
+ "rcshowhidemine-hide": "लà¥\81à¤\95ाऽ",
"rcshowhidecategorization-show": "धेकाउन्या",
"rcshowhidecategorization-hide": "लुकाउन्या",
"rclinks": "पछिल्ला $1 परिवर्तनहरू पछाडिका $2 दिनहरूमी<br />$3",
"uploadstash-nofiles": "तमरा कोइ पनि स्टाश गर्याका फाइलहरू नाइथिन् ।",
"uploadstash-badtoken": "त्यो कार्य असफलभयो , सायद तमरो सम्पादन अधिकार समाप्त भयो । पुन: प्रयास गर ।",
"uploadstash-refresh": "फाइलहरूको सूची ताजा गर्न्या",
- "license-header": "à¤\95à¥\8bà¤\87 à¤\95à¥\87à¤\87 नाà¤\87थिन",
+ "license-header": "à¤\86à¤\9cà¥\8dà¤\9eापतà¥\8dर दिनà¥\8dनाà¤\9b़",
"listfiles-summary": "यै खास पानाले अपलोड गर्याका सबै फाइलहरू धेकाउन्छ ।",
"imgfile": "चित्र",
"listfiles_count": "संस्करणहरू",
"filehist-thumb": "थम्बनेल",
"filehist-thumbtext": "थम्बनेल $1 संस्करणको रुपमी",
"filehist-user": "प्रयोगकर्ता",
- "filehist-dimensions": "à¤\86à¤\95ारहरà¥\82",
+ "filehist-dimensions": "à¤\86याम",
"filehist-comment": "टिप्पणी",
"imagelinks": "फाइलको प्रयोगहरु",
"linkstoimage": "यै चित्रमी निम्न{{PLURAL:$1|पाना जोडिनान{{PLURAL:$1|}}|$1 पानाहरू जोडिनान्}}:",
"deadendpagestext": "निम्न पानाहरू {{SITENAME}}मी रह्याका अरु पानाहरूसँग जोडिदाइनन् ।",
"protectedpagesempty": "यै बेला यी नियम बठे कुनै पाना लै शुरक्षित नाइथिन्",
"usereditcount": "$1 {{PLURAL:$1|सम्पादन|सम्पादनहरू}}",
- "newpages": "नयाà¤\81 पानाहरà¥\82",
+ "newpages": "नà¥\8cला पनà¥\8dनाà¤\85न",
"move": "नाम बदल",
"movethispage": "पानाको नाम बदल्न्या",
"notargettext": "यै कार्यका लेखाई तमीले कुनै लक्षित पानो वा प्रयोगकर्ता निर्दिष्ट गर्याको छैनौ ।",
"booksources-text": "तल दियाको सूची नौला तथा पूराना किताब बेच्न्या लगायत तमीले खोज्याका किताबका बारेमी थप जानकारी भयाका अन्य साइटका लिंकहरू हुन् ।",
"log": "लगहरू",
"all-logs-page": "सब्बै सार्वजनिक लगहरू",
- "allarticles": "सबà¥\8dबà¥\88 लà¥\87à¤\96हरà¥\82",
- "allpagessubmit": "à¤\9cानà¥\8dया",
+ "allarticles": "सपà¥\8dपà¥\88 पनà¥\8dनाà¤\85न",
+ "allpagessubmit": "à¤\9cाऽ",
"allpagesprefix": "यी सुरुका अक्षरसहितका पानाहरू हेद्या:",
"categories": "श्रेणीहरू",
"listusers-noresult": "प्रयोगकर्ता भेटियानन्",
"month": "महिना बठे (लै पैल्ली):",
"year": "वर्ष बठे( लौ पैल्ली):",
"sp-contributions-toponly": "नवीनतम संशोधनका सम्पादनहरू मात्र धेकाओ",
- "whatlinkshere": "याà¤\81à¤\96ाà¤\87 à¤\95à¥\80 à¤\9cà¥\81डन्छ",
- "whatlinkshere-title": "$1 सित à¤\9cà¥\8bडियाà¤\95ा पानाहरà¥\82",
+ "whatlinkshere": "याà¤\81à¤\96ाà¤\87 à¤\95ि à¤\9cà¥\8bणà¥\80न्छ",
+ "whatlinkshere-title": "$1 सित à¤\9cà¥\8bडियाऽ पनà¥\8dनाà¤\85न",
"whatlinkshere-page": "पानो",
"linkshere": "निम्न पानाहरू '''[[:$1]]''' मी जुडन्छ :",
"nolinkshere-ns": "चुनियाको नामस्थानमी '''[[:$1]]''' सित जुड्न्या पानाहरू नाइथिन्।",
"whatlinkshere-links": "← लिंकहरू",
"whatlinkshere-hideredirs": "$1 पुन:निर्देशित हुन्छ",
"whatlinkshere-hidetrans": "$1 सम्मील",
- "whatlinkshere-hidelinks": "$1 लिङ्कहरू",
- "whatlinkshere-hideimages": "$1 फाइल लिंकहरू",
+ "whatlinkshere-hidelinks": "$1 लिङ्क",
+ "whatlinkshere-hideimages": "$1 फाइलआ लिङ्कअन",
"whatlinkshere-filters": "छानियाका",
"ipbreason-dropdown": "* ब्लक गर्नुका समान्य कारणहरू\n** झूटो सूचना दियाको\n** पानानबठे सामाग्रीहरू हटायाको\n** बाहिरी जालक्षेत्र (sites)सित नचाहिंदो लिङ्क गर्याको \n** पानानमी बकवास/गाली-गलौच हाल्याको\n** भै धेकाउने व्यवहार/उत्पीडन (सताउने कार्य) गर्याको\n** धेरै गलत खाताहरू बनायाको\n** प्रयोगकर्ता नाम अस्वीकार्य",
"ipboptions": "२ घण्टाहरू:2 hours,१ दिन :1 day,३ दिनहरू:3 days,१ हप्ता:1 week,२ हप्ताहरू:2 weeks,१ महिना:1 month,३ महिनाहरू:3 months,६ महिनाहरू:6 months,१ वर्ष:1 year,अनगिन्ती:infinite",
"tooltip-pt-mycontris": "{{GENDER:|तमरा}} योगदानअनऐ सूची",
"tooltip-pt-login": "तमलाई प्रवेशगद्द सुझाव दिइन्छ ; याद अरऽ यो जरुरी आथिन भण्या ।",
"tooltip-pt-logout": "बाहिर निस्कन्या (लग आउट)",
- "tooltip-pt-createaccount": "तमलाई खाता बनौन लै लग इन अद्द हम हौसला अद्दाउ; काइकि, यो अनिवार्य नाइथी भण्या ।",
+ "tooltip-pt-createaccount": "तमलाई खाता बनौन लै लगइन अद्द लै हम हौसला अद्दाउ; यद्यपि, यो अनिवार्य नाइथीन।",
"tooltip-ca-talk": "सामाग्री पृष्ठबारेमी कुरणिकाआनी",
"tooltip-ca-edit": "येइ पन्ना सम्पादन गरऽ",
"tooltip-ca-addsection": "नयाँ खण्ड सुरु अरिदिय",
"tooltip-n-currentevents": "हालैका घटनाको बारेमी पृष्ठभूमि जानकारी पत्ता लागाइदिय",
"tooltip-n-recentchanges": "विकिमी हालै अरियाका फेरबदलै शुचि ।",
"tooltip-n-randompage": "क्रमरहित पन्ना खोलऽ",
- "tooltip-n-help": "खोज्जु पड्या ठौर ।",
+ "tooltip-n-help": "à¤\96à¥\8bà¤\9cà¥\8dà¤\9cà¥\81 पडà¥\8dडà¥\8dया ठà¥\8cर ।",
"tooltip-t-whatlinkshere": "सप्पै विकि पन्नाअनै शुचि जो याँखाइ जोणीजान",
"tooltip-t-recentchangeslinked": "यै पानामी जोडियाका पानामी अहिलको परिवर्तन",
"tooltip-feed-atom": "यै पानाकी लेखा एक एटम फिड",
- "tooltip-t-contributions": "{{GENDER:$1|यिन पà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता}}à¤\95ा यà¥\8bà¤\97दानहरà¥\82à¤\95à¥\8b सà¥\82à¤\9aà¥\80 हà¥\87रपà¥\81à¤\88",
+ "tooltip-t-contributions": "{{GENDER:$1|यिन पà¥\8dरयà¥\8bà¤\97à¤\95रà¥\8dता}}ऽ यà¥\8bà¤\97दानन à¤\90 शà¥\81à¤\9aि",
"tooltip-t-upload": "फाइल अप्लोड अरऽ",
"tooltip-t-specialpages": "सब्बै खास-खास पन्नाअनै सूची",
"tooltip-t-print": "येइ पन्नाऽ छाप्द मिल्ल्या संस्करण",
"continue-editing": "Và int la zôna 'd mudéfica",
"previewconflict": "La vésta la cumbîna cun al tèst int la zôna 'd mudéfica tèst ché d'ed sōver e l'é cme la srà la pàgina s'ed decéd ed clichêr insém a \"Sêlva la pàgina\" in cól mumèint ché.",
"session_fail_preview": "A's în dispiêş. An n'é mìa stê pusébil registrêr la mudéfica perchè a 's în pêrsi al j infurmasiòun relatîvi a la sesiòun. Ét prés èser stê destachê. <strong>Contròla s' t'é incòra coleghê</strong>. Se al problēma 'l cunténva, a 's pōl pruvêr [[Special:UserLogout|ed coleghêres]] e fêr un ingrès nōv, contròla ânch se al tó navigadōr l' acèta i cookie da cól sît ché.",
- "session_fail_preview_html": "'''An n'é mìa stê pusébil registrêr la mudéfica perchè în andêdi persi al j infurmasiòun relatîvi a la sesiòun.'''\n\n''Pôst che in {{SITENAME}} a gh'é al permès ed druvêr l' HTML sèinsa lémit, an 's pōl mìa guardêr préma la pàgina mudifichêda; a 's trâta ed 'n'amzûra 'd sicurèsa cûntra j atâch JavaScript.''\n\n''' Se còst l'é un tentatîv legétim ed mudéfica, pruvêr incòra. Se al prublēma l'armâgn, a 's pōl pruvêr a [[Special:UserLogout|sarêr al colegamèint]] e fêr un nōv ingrès.'''",
+ "session_fail_preview_html": "A's în deispiêş. An n'é mìa stê pusébil registrêr la mudéfica perchè în andêdi persi al j infurmasiòun relatîvi a la sesiòun. \n\n<em> Dâto che {{SITENAME}} al gh'à un HTML grēz inviê a a 'ss è pêrs dal j infurmasiòun ed la sesiòun, la vésta préma ed salvêr l'è lughêda per prudèinsa cûntra j atâch JavaScript.</em>\n\n<strong>Se ' s trâta 'd un tentatîv normêl ed vèder còl che t'è fât préma 'd salvêrel, tōrna pruvêr.</strong>\nSe gh'è incòra al problēma, ét pō pruvêr a [[Special:UserLogout|scoleghêret]] e fêr un nōv ingrès, mó préma contròla che 'l tó navigadōr al tóga i cookie da cól sît ché.",
"token_suffix_mismatch": "'''La mudéfica an n'é mìa stêda salvêda perchè al ''client'' l'à fât vèder ed gestîr in môd e-sbaliê i carâter di pûn e dal virgûli int al ''token'' lighê a la mudéfica. Per schivşêr di pusébil erōr int al tèst ed la pàgina, è stê rifiutê tóta la mudéfica. Dla vôlti cla situasiòun ché la pōl sucēder quând a vînen druvê soquânt servési ''proxy'' sèinsa nòm via internèt che preşèinten di ''bug''.'''",
"edit_form_incomplete": "'''Soquânti pêrt dal môdul ed mudéfica în mìa rivêdi al ''server''; controlêr che al mudéfichi sién intâti e turnêr a pruvêr'''",
"editing": "Mudéfica ed $1",
"createacct-yourpasswordagain-ph": "Εισαγωγή κωδικού ξανά",
"userlogin-remembermypassword": "Να διατηρούμαι μόνιμα σε σύνδεση",
"userlogin-signwithsecure": "Χρησιμοποιείστε ασφαλή σύνδεση",
+ "cannotlogin-title": "Δεν μπορώ να συνδεθώ",
+ "cannotlogin-text": "Η σύνδεση δεν είναι δυνατή.",
"cannotloginnow-title": "Δεν μπορείτε να συνδεθείτε τώρα",
"cannotloginnow-text": "Η σύνδεση δεν είναι δυνατή όταν χρησιμοποιείτε την $1.",
"yourdomainname": "Το domain σας:",
"mergehistory-fail-bad-timestamp": "Η χρονική σήμανση δεν είναι έγκυρη.",
"mergehistory-fail-invalid-source": "Η πηγή σελίδας δεν είναι έγκυρη.",
"mergehistory-fail-invalid-dest": "Η σελίδα προορισμού δεν είναι έγκυρη.",
+ "mergehistory-fail-permission": "Μη επαρκή δικαιώματα για τη συγχώνευση του ιστορικού.",
+ "mergehistory-fail-self-merge": "Η πηγή και ο προορισμός των σελίδων είναι ο ίδιος.",
"mergehistory-fail-toobig": "Δεν είναι δυνατό να πραγματοποιηθεί η συγχώνευση ιστορικών, καθώς πάνω από $1 {{PLURAL:$1|αναθεώρηση|αναθεωρήσεις}} θα μετακινούνταν.",
"mergehistory-no-source": "Η σελίδα πηγής $1 δεν υπάρχει.",
"mergehistory-no-destination": "Η σελίδα προορισμού $1 δεν υπάρχει.",
"grant-group-high-volume": "Εκτέλεση υψηλής έντασης δραστηριότητας",
"grant-group-customization": "Ρυθμίσεις και προτιμήσεις",
"grant-group-administration": "Εκτέλεση διαχειριστικών ενεργειών",
+ "grant-group-private-information": "Πρόσβαση σε ιδιωτικά δεδομένα σχετικά με εσάς",
+ "grant-group-other": "Διάφορες δραστηριότητες",
"grant-blockusers": "Φραγή και αναίρεση φραγής χρηστών",
"grant-createaccount": "Δημιουργία λογαριασμών",
"grant-createeditmovepage": "Δημιουργία, επεξεργασία και μετακίνηση σελίδων",
"grant-highvolume": "Υψηλής έντασης επεξεργασία",
"grant-oversight": "Απόκρυψη χρηστών και καταστολή αναθεωρήσεων",
"grant-patrol": "Περιπολία αλλαγών σε σελίδες",
+ "grant-privateinfo": "Πρόσβαση σε προσωπικές πληροφορίες",
"grant-protect": "Προστασία και κατάργηση προστασίας σελίδων",
"grant-rollback": "Η επαναφορά αλλαγών σε σελίδες",
"grant-sendemail": "Αποστολή μηνύματος ηλεκτρονικού ταχυδρομείου σε άλλους χρήστες",
"action-applychangetags": "εφαρμογή ετικετών μαζί με τις αλλαγές σας",
"action-changetags": "πρόσθεση και αφαίρεση αυθαίρετων ετικετών σε μεμονωμένες εκδόσεις και καταχωρήσεις καταγραφών",
"action-deletechangetags": "διαγράψετε ετικέτες από τη βάση δεδομένων",
+ "action-purge": "εκκαθάριση αυτής της σελίδας",
"nchanges": "$1 {{PLURAL:$1|αλλαγή|αλλαγές}}",
"enhancedrc-since-last-visit": "$1 {{PLURAL:$1|από την τελευταία επίσκεψη}}",
"enhancedrc-history": "ιστορικό",
"uploadstash-badtoken": "Εκτέλεση της εν λόγω ενέργειας απέτυχε, ίσως επειδή τα διαπιστευτήριά επεξεργασίας σας έχουν λήξει. Παρακαλούμε δοκιμάστε ξανά.",
"uploadstash-errclear": "Η εκκαθάριση των αρχείων απέτυχε.",
"uploadstash-refresh": "Ανανεώσετε τη λίστα των αρχείων",
+ "uploadstash-thumbnail": "προβολή μικρογραφίας",
"invalid-chunk-offset": "Άκυρο κομμάτι όφσετ",
"img-auth-accessdenied": "Δεν επετράπη η πρόσβαση",
"img-auth-nopathinfo": "Λείπει το PATH_INFO.\nΟ διακομιστής σας δεν είναι ρυθμισμένος για να περάσει αυτές τις πληροφορίες.\nΜπορεί να είναι βασισμένος σε CGI και να μην υποστηρίζει img_atuh.\nΔείτε https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization",
"apisandbox-helpurls": "Σύνδεσμοι βοήθειας",
"apisandbox-examples": "Παραδείγματα",
"apisandbox-dynamic-parameters": "Πρόσθετες παράμετροι",
+ "apisandbox-dynamic-parameters-add-label": "Προσθήκη παραμέτρου:",
"apisandbox-dynamic-parameters-add-placeholder": "Ονομασία παραμέτρου",
"apisandbox-dynamic-error-exists": "Η παράμετρος με την ονομασία \"$1\" υπάρχει ήδη",
"apisandbox-submit-invalid-fields-title": "Κάποια από τα πεδία δεν είναι έγκυρα",
"listgrouprights-namespaceprotection-header": "Περιορισμοί ονοματοχώρων",
"listgrouprights-namespaceprotection-namespace": "Ονοματοχώρος",
"listgrouprights-namespaceprotection-restrictedto": "Δικαίωμα(τα) που επιτρέπει(ουν) σε χρήστη να επεξεργαστεί",
+ "listgrants": "Επιχορηγήσεις",
+ "listgrants-grant": "Επιχορήγηση",
"listgrants-rights": "Δικαιώματα",
"trackingcategories": "Κατηγορίες παρακολούθησης",
"trackingcategories-summary": "Αυτή η σελίδα εμφανίζει τις κατηγορίες παρακολούθησης το περιεχόμενο των οποίων συμπληρώνεται αυτόματα από το λογισμικό MediaWiki. Τα ονόματά τους μπορεί να αλλαχθούν με την αλλαγή των σχετικών μηνυμάτων συστήματος στον ονοματοχώρο {{ns:8}}.",
"tog-enotifminoredits": "Email me also for minor edits of pages and files",
"tog-enotifrevealaddr": "Reveal my email address in notification emails",
"tog-shownumberswatching": "Show the number of watching users",
- "tog-oldsig": "Existing signature:",
+ "tog-oldsig": "Your existing signature:",
"tog-fancysig": "Treat signature as wikitext (without an automatic link)",
"tog-uselivepreview": "Use live preview",
"tog-forceeditsummary": "Prompt me when entering a blank edit summary",
"tog-showhiddencats": "Show hidden categories",
"tog-norollbackdiff": "Don't show diff after performing a rollback",
"tog-useeditwarning": "Warn me when I leave an edit page with unsaved changes",
- "tog-prefershttps": "Always use a secure connection when logged in",
+ "tog-prefershttps": "Always use a secure connection while logged in",
"underline-always": "Always",
"underline-never": "Never",
"underline-default": "Skin or browser default",
"category-file-count-limited": "The following {{PLURAL:$1|file is|$1 files are}} in the current category.",
"listingcontinuesabbrev": "cont.",
"index-category": "Indexed pages",
- "noindex-category": "Noindexed pages",
+ "noindex-category": "Non-indexed pages",
"broken-file-category": "Pages with broken file links",
"categoryviewer-pagedlinks": "($1) ($2)",
"category-header-numerals": "$1–$2",
"newwindow": "(opens in new window)",
"cancel": "Cancel",
"moredotdotdot": "More...",
- "morenotlisted": "This list is not complete.",
+ "morenotlisted": "This list may be incomplete.",
"mypage": "Page",
"mytalk": "Talk",
"anontalk": "Talk",
"createacct-yourpasswordagain-ph": "Enter password again",
"userlogin-remembermypassword": "Keep me logged in",
"userlogin-signwithsecure": "Use secure connection",
+ "cannotlogin-title": "Cannot log in",
+ "cannotlogin-text": "Logging in is not possible.",
"cannotloginnow-title": "Cannot log in now",
"cannotloginnow-text": "Logging in is not possible when using $1.",
+ "cannotcreateaccount-title": "Cannot create accounts",
+ "cannotcreateaccount-text": "Direct account creation is not enabled on this wiki.",
"yourdomainname": "Your domain:",
"password-change-forbidden": "You cannot change passwords on this wiki.",
"externaldberror": "There was either an authentication database error or you are not allowed to update your external account.",
"botpasswords-updated-body": "The bot password for bot name \"$1\" of user \"$2\" was updated.",
"botpasswords-deleted-title": "Bot password deleted",
"botpasswords-deleted-body": "The bot password for bot name \"$1\" of user \"$2\" was deleted.",
- "botpasswords-newpassword": "The new password to log in with <strong>$1</strong> is <strong>$2</strong>. <em>Please record this for future reference.</em>",
+ "botpasswords-newpassword": "The new password to log in with <strong>$1</strong> is <strong>$2</strong>. <em>Please record this for future reference.</em> <br> (For old bots which require the login name to be the same as the eventual username, you can also use <strong>$3</strong> as username and <strong>$4</strong> as password.)",
"botpasswords-no-provider": "BotPasswordsSessionProvider is not available.",
"botpasswords-restriction-failed": "Bot password restrictions prevent this login.",
"botpasswords-invalid-name": "The username specified does not contain the bot password separator (\"$1\").",
"invalid-content-data": "Invalid content data",
"content-not-allowed-here": "\"$1\" content is not allowed on page [[$2]]",
"editwarning-warning": "Leaving this page may cause you to lose any changes you have made.\nIf you are logged in, you can disable this warning in the \"{{int:prefs-editing}}\" section of your preferences.",
+ "editpage-invalidcontentmodel-title": "Content model not supported",
+ "editpage-invalidcontentmodel-text": "The content model \"$1\" is not supported.",
"editpage-notsupportedcontentformat-title": "Content format not supported",
"editpage-notsupportedcontentformat-text": "The content format $1 is not supported by the content model $2.",
"content-model-wikitext": "wikitext",
"tag-filter": "[[Special:Tags|Tag]] filter:",
"tag-filter-submit": "Filter",
"tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Tag|Tags}}]]: $2)",
+ "tag-mw-contentmodelchange": "content model change",
+ "tag-mw-contentmodelchange-description": "Edits that [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel change the content model] of a page",
"tags-title": "Tags",
"tags-intro": "This page lists the tags that the software may mark an edit with, and their meaning.",
"tags-tag": "Tag name",
"tags-actions-header": "Actions",
"tags-active-yes": "Yes",
"tags-active-no": "No",
- "tags-source-extension": "Defined by an extension",
+ "tags-source-extension": "Defined by the software",
"tags-source-manual": "Applied manually by users and bots",
"tags-source-none": "No longer in use",
"tags-edit": "edit",
"ok": "Bone",
"retrievedfrom": "Elŝutita el \"$1\"",
"youhavenewmessages": "{{PLURAL:$3|Vi havas}} $1 ($2).",
- "youhavenewmessagesfromusers": "Riceviĝis $1 de {{PLURAL:$3|alia uzanto|$3 uzantoj}} ($2).\n\nVi havas $1 de {{PLURAL:$3|alia uzanto|$3 uzantoj}} ($2).",
+ "youhavenewmessagesfromusers": "Vi havas {{PLURAL:$1|mesaĝon|$1 mesaĝojn}} de {{PLURAL:$3|alia uzanto|$3 uzantoj}} ($2).",
"youhavenewmessagesmanyusers": "Riceviĝis $1 de multaj uzantoj ($2).",
"newmessageslinkplural": "{{PLURAL:$1|nova mesaĝo|999=novaj mesaĝoj}}",
"newmessagesdifflinkplural": "$1 {{PLURAL:$1|ŝanĝo|ŝanĝoj}}",
"createacct-yourpasswordagain-ph": "Retajpu pasvorton",
"userlogin-remembermypassword": "Memori mian ensaluton",
"userlogin-signwithsecure": "Uzu sekurigitan konekton",
+ "cannotlogin-title": "Ne eblas ensaluti",
+ "cannotlogin-text": "Ensaluto estas neebla.",
"cannotloginnow-title": "Nuntempe ne eblas ensaluti",
"cannotloginnow-text": "Ne eblas ensaluti dum uzado de $1.",
+ "cannotcreateaccount-title": "Ne eblas krei konton",
+ "cannotcreateaccount-text": "Senpera kreo de uzantokonto ne estas enŝaltita en ĉi tiu vikio.",
"yourdomainname": "Via domajno",
"password-change-forbidden": "Ve ne povas ŝanĝi pasvortojn en ĉi tiu vikio.",
"externaldberror": "Aŭ estis datenbaza eraro rilate al ekstera aŭtentikigado, aŭ vi ne rajtas ĝisdatigi vian eksteran konton.",
"action-applychangetags": "aldoni etikedojn al viaj propraj ŝanĝoj",
"action-changetags": "aldoni kaj forigi arbitrajn etikedojn ĉe unuopaj revizioj kaj protokoleroj",
"action-deletechangetags": "Forigi etikedojn de la datenbazo.",
+ "action-purge": "malplenigi servilan kaŝmemoron",
"nchanges": "$1 {{PLURAL:$1|ŝanĝo|ŝanĝoj}}",
"enhancedrc-since-last-visit": "$1 {{PLURAL:$1|ekde lasta vizito}}",
"enhancedrc-history": "historio",
"file-thumbnail-no": "La dosiernomo komencas kun <strong>$1</strong>.\nĜi ŝajnas kiel bildo de malgrandigita grandeco ''(thumbnail)''.\nSe vi havas ĉi tiun bildon en plena distingivo, alŝutu ĉi tiun, alikaze bonvolu ŝanĝi la dosieran nomon.",
"fileexists-forbidden": "Dosiero kun ĉi tiu nomo jam ekzistas kaj ne povas anstataŭigi ĝin.\nSe vi ankoraŭ volas alŝuti vian dosieron, bonvolu reprovi kun nova nomo.\n[[File:$1|thumb|center|$1]]",
"fileexists-shared-forbidden": "Dosiero kun ĉi tia nomo jam ekzistas en la komuna dosierujo.\nSe vi ankoraŭ volas alŝuti vian dosieron, bonvolu retroigi kaj uzi novan nomon.[[File:$1|thumb|center|$1]]",
+ "fileexists-no-change": "La alŝutaĵo estas preciza kopio de la nuna versio de <strong>[[:$1]]</strong>.",
+ "fileexists-duplicate-version": "La alŝutaĵo estas preciza kopio de {{PLURAL:$2|malnova versio|malnovaj versioj}} de <strong>[[:$1]]</strong>.",
"file-exists-duplicate": "Ĉi tiu dosiero estas duplikato de la {{PLURAL:$1|jena dosiero|jenaj dosieroj}}:",
"file-deleted-duplicate": "Duplikata dosiero de ĉi tiu dosiero ([[:$1]]) estis antaŭe forigita. Vi legu la forigan historion de tiu dosiero antaŭ provi realŝuti ĝin.",
"file-deleted-duplicate-notitle": "Dosiero identa al ĉi tiu dosiero estis forigita antaŭ nelonge kaj la titolo estis subpremita.\nVi demandu iun, kiu havas la eblecon, rigardi la subpremitajn dosierajn datojn, por kontroli la situacion antaŭ rea alŝutado.",
"upload-http-error": "HTTP-eraro okazis: $1",
"upload-copy-upload-invalid-domain": "Kopio-alŝutoj ne disponiĝas el ĉi tiu domajno.",
"upload-foreign-cant-upload": "Tiu vikio ne estas agorita por alŝuti alŝutitan dosieron al la petita fora dosierdeponejo.",
- "upload-foreign-cant-load-config": "La ŝarĝado de agordo pri dosieran alŝuton malsukcesis por la fora dosiera deponejo.",
+ "upload-foreign-cant-load-config": "Malsukcesis ŝargi la agordon por dosier-alŝutoj al ekstera dosier-deponejo.",
"upload-dialog-disabled": "Alŝutoj de dosiero per ĉi tiun dialogon estas malfunkciigita sur ĉi tiu vikio.",
"upload-dialog-title": "Alŝuti dosieron",
"upload-dialog-button-cancel": "Nuligi",
"notargettext": "Vi ne precizigis, kiun paĝon aŭ uzanton priumi.",
"nopagetitle": "Nenia cela paĝo",
"nopagetext": "La cela paĝo kiun vi enigis ne ekzistas.",
- "pager-newer-n": "{{PLURAL:$1|pli nova 1|pli novaj $1}}",
- "pager-older-n": "{{PLURAL:$1|pli malnova 1|pli malnovaj $1}}",
+ "pager-newer-n": "{{PLURAL:$1|pli novan 1|pli novajn $1}}",
+ "pager-older-n": "{{PLURAL:$1|pli malnovan 1|pli malnovajn $1}}",
"suppress": "Forigu",
"querypage-disabled": "Tiu ĉi speciala paĝo estas malfunkciigita pro rendimentaj kialoj.",
"apihelp": "Helpo pri API",
"sp-contributions-newbies-sub": "Kontribuoj de novaj uzantoj. Forigitaj paĝoj ne estas montritaj.",
"sp-contributions-newbies-title": "Kontribuoj de novaj uzantoj",
"sp-contributions-blocklog": "protokolo de forbaroj",
- "sp-contributions-suppresslog": "kaŝitaj kontribuoj de uzant{{GENDER:$1||in}}o",
- "sp-contributions-deleted": "forigitaj kontribuoj de uzant{{GENDER:$1||in}}o",
+ "sp-contributions-suppresslog": "kaŝitaj kontribuoj de {{GENDER:$1|uzanto}}",
+ "sp-contributions-deleted": "forigitaj kontribuoj de {{GENDER:$1|uzanto}}",
"sp-contributions-uploads": "alŝutoj",
"sp-contributions-logs": "protokoloj",
"sp-contributions-talk": "diskuto",
"createacct-yourpasswordagain-ph": "Repite la contraseña",
"userlogin-remembermypassword": "Mantener mi sesión iniciada",
"userlogin-signwithsecure": "Usar conexión segura",
+ "cannotlogin-title": "No se puede iniciar sesión",
"cannotloginnow-title": "No se puede iniciar sesión ahora",
"cannotloginnow-text": "No se puede iniciar sesión cuando se usa $1.",
+ "cannotcreateaccount-title": "No se pueden crear cuentas",
+ "cannotcreateaccount-text": "La creación directa de cuentas no está activada en este wiki.",
"yourdomainname": "Tu dominio:",
"password-change-forbidden": "No puedes cambiar las contraseñas en este wiki.",
"externaldberror": "Hubo un error de autenticación en la base de datos, o bien no tienes autorización para actualizar tu cuenta externa.",
"usercreated": "{{GENDER:$3|Registrado|Registrada}} el $1 a las $2",
"newpages": "Páginas nuevas",
"newpages-submit": "Mostrar",
- "newpages-username": "Nombre de usuario",
+ "newpages-username": "Nombre de usuario:",
"ancientpages": "Páginas más antiguas",
"move": "Trasladar",
"movethispage": "Trasladar esta página",
"querypage-disabled": "Esta página especial está deshabilitada por motivos de rendimiento.",
"apihelp": "Ayuda de la API",
"apihelp-no-such-module": "No se encontró el módulo \"$1\".",
- "apisandbox": "Zona de pruebas API",
+ "apisandbox": "Zona de pruebas de la API",
"apisandbox-jsonly": "Se requiere JavaScript para utilizar la zona de pruebas de API.",
"apisandbox-api-disabled": "La API está desactivada en este sitio.",
"apisandbox-intro": "Usa esta página para experimentar con la <strong>API de servicio web de MediaWiki</strong>.\nPara más detalles sobre el uso de la API, visita [[mw:API:Main page|su documentación]]. Ejemplo: [https://www.mediawiki.org/wiki/API#A_simple_example obtener el contenido de una Página principal]. Selecciona una acción para ver más ejemplos.\n\nObserva que, aunque sea una página de pruebas, las acciones que realices en esta página pueden modificar el wiki.",
"pageinfo-article-id": "Identificador de la página",
"pageinfo-language": "Idioma de la página",
"pageinfo-content-model": "Modelo de contenido de la página",
+ "pageinfo-content-model-change": "cambiar",
"pageinfo-robot-policy": "Indización por robots",
"pageinfo-robot-index": "Permitido",
"pageinfo-robot-noindex": "No permitido",
"yourpasswordagain": "تکرار گذرواژه:",
"createacct-yourpasswordagain": "گذرواژه را دوباره وارد کنید",
"createacct-yourpasswordagain-ph": "گذرواژه را وارد کنید برای بار دوم",
- "remembermypassword": "گذرواژه را (تا حداکثر $1 {{PLURAL:$1|روز|روز}}) در این رایانه به خاطر بسپار",
"userlogin-remembermypassword": "من را واردشده نگهدار",
"userlogin-signwithsecure": "از ورود امن استفاده کنید",
"cannotloginnow-title": "الان امکان وررود به سامانه نیست",
"minoredit": "این ویرایش، جزئی است",
"watchthis": "پیگیری این صفحه",
"savearticle": "صفحه ذخیره شود",
- "savechanges": "ذخیرهٔ تغییرات",
+ "savechanges": "ذخیره کردن تغییرات",
"publishpage": "انتشار صفحه",
"publishchanges": "انتشار تغییرات",
"preview": "پیشنمایش",
"allinnamespace": "همهٔ صفحات (فضای نام $1)",
"allpagessubmit": "برو",
"allpagesprefix": "نمایش صفحههای دارای پیشوند:",
- "allpagesbadtitle": "عÙ\86Ù\88اÙ\86 صÙ\81ØÙ\87Ù\94 دادÙ\87â\80\8cشدÙ\87 Ù\86اÙ\85عتبر است Û\8cا اÛ\8cÙ\86Ú©Ù\87 داراÛ\8c Ù¾Û\8cØ´Ù\88Ù\86دÛ\8c بÛ\8cÙ\86â\80\8cزباÙ\86Û\8c Û\8cا بÛ\8cÙ\86â\80\8cÙ\88Û\8cÚ©Û\8câ\80\8cاÛ\8c است. Ù\85Ù\85Ú©Ù\86 است Ù\86Ù\88Û\8cسÙ\87â\80\8cÙ\87اÛ\8cÛ\8c بدارد Ú©Ù\87 Ù\86Ù\85Û\8câ\80\8cتÙ\88اÙ\86 از Ø¢Ù\86Ù\87ا در عنوان صفحات استفاده کرد.",
+ "allpagesbadtitle": "عÙ\86Ù\88اÙ\86 صÙ\81ØÙ\87Ù\94 دادÙ\87â\80\8cشدÙ\87 Ù\86اÙ\85عتبر است Û\8cا اÛ\8cÙ\86Ú©Ù\87 داراÛ\8c Ù¾Û\8cØ´Ù\88Ù\86دÛ\8c بÛ\8cÙ\86â\80\8cزباÙ\86Û\8c Û\8cا بÛ\8cÙ\86â\80\8cÙ\88Û\8cÚ©Û\8câ\80\8cاÛ\8c است. Ù\85Ù\85Ú©Ù\86 است Ù\86Ù\88Û\8cسÙ\87â\80\8cÙ\87اÛ\8cÛ\8c داشتÙ\87 Û\8cاشد Ú©Ù\87 از Ø¢Ù\86Ù\87ا Ù\86Ù\85Û\8câ\80\8cتÙ\88اÙ\86 در عنوان صفحات استفاده کرد.",
"allpages-bad-ns": "{{SITENAME}} دارای فضای نام «$1» نیست.",
"allpages-hide-redirects": "پنهانکردن تغییرمسیرها",
"cachedspecial-viewing-cached-ttl": "شما در حال مشاهدهٔ نسخهای از این صفحه که در میانگیر قرار دارد هستید که ممکن است برای $1 قبل باشد.",
"allmessagesdefault": "متن پیشفرض پیغام",
"allmessagescurrent": "متن کنونی پیغام",
"allmessagestext": "این فهرستی از پیغامهای سامانهای موجود در فضای نام مدیاویکی است.\nچنانچه مایل به مشارکت در محلیسازی مدیاویکی هستید لطفاً [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation محلیسازی مدیاویکی] و [https://translatewiki.net translatewiki.net] را ببینید.",
- "allmessagesnotsupportedDB": "این صفحه نمیتواند استفاده شود به این دلیل که <bdi>'''$wgUseDatabaseMessages'''</bdi> غیرفعال شدهاست.",
+ "allmessagesnotsupportedDB": "این صفحه نمیتواند استفاده شود به این دلیل که <strong>$wgUseDatabaseMessages</strong> غیرفعال شده است.",
"allmessages-filter-legend": "پالایه",
"allmessages-filter": "پالودن بر اساس وضعیت شخصیسازی:",
"allmessages-filter-unmodified": "تغییر نیافته",
"Matma Rex",
"Dcausse",
"Lucas",
- "Mabroukb"
+ "Mabroukb",
+ "Pymouss"
]
},
"tog-underline": "Soulignement des liens :",
"createacct-yourpasswordagain-ph": "Entrez à nouveau le mot de passe",
"userlogin-remembermypassword": "Garder ma session active",
"userlogin-signwithsecure": "Utiliser une connexion sécurisée",
+ "cannotlogin-title": "Impossible de se connecter",
+ "cannotlogin-text": "La connexion n’est pas possible.",
"cannotloginnow-title": "Impossible de se connecter maintenant",
"cannotloginnow-text": "La connexion n’est pas possible en utilisant $1.",
+ "cannotcreateaccount-title": "Création de comptes impossible",
+ "cannotcreateaccount-text": "La création directe de comptes utilisateurs n’est pas activée sur ce wiki.",
"yourdomainname": "Votre domaine :",
"password-change-forbidden": "Vous ne pouvez pas modifier les mots de passe sur ce wiki.",
"externaldberror": "Soit une erreur s’est produite sur la base de données d’authentification, soit vous n’êtes pas autorisé à mettre à jour votre compte externe.",
"prefs-emailconfirm-label": "Confirmation du courriel :",
"youremail": "Courriel :",
"username": "{{GENDER:$1|Nom d'utilisateur|Nom d'utilisatrice}} :",
- "prefs-memberingroups": "{{GENDER:$2|Membre}} {{PLURAL:$1|du groupe|des groupes}} :",
+ "prefs-memberingroups": "{{GENDER:$2|Membre}} {{PLURAL:$1|du groupe|des groupes}}:",
"prefs-registration": "Date d'inscription :",
"yourrealname": "Nom réel :",
"yourlanguage": "Langue :",
"fileexists-forbidden": "Un fichier avec ce nom existe déjà et ne peut pas être écrasé.\nSi vous voulez toujours importer votre fichier, veuillez revenir en arrière et utiliser un autre nom. \n[[File:$1|thumb|center|$1]]",
"fileexists-shared-forbidden": "Un fichier portant ce nom existe déjà dans le dépôt de fichiers partagé.\nSi vous voulez toujours importer votre fichier, veuillez revenir en arrière et utiliser un autre nom. \n[[File:$1|thumb|center|$1]]",
"fileexists-no-change": "Le fichier téléchargé est une copie exacte de la version actuelle de <strong>[[:$1]]</strong>",
- "fileexists-duplicate-version": "Le fichier téléchargé est une copie exacte {{PLURAL:$2|d'une version précédente|de versions précédentes}} de <strong>[[:$1]]</strong>.",
+ "fileexists-duplicate-version": "Le fichier téléversé est une copie exacte {{PLURAL:$2|d'une version précédente|de versions précédentes}} de <strong>[[:$1]]</strong>.",
"file-exists-duplicate": "Ce fichier est un doublon {{PLURAL:$1|du fichier suivant|des fichiers suivants}} :",
"file-deleted-duplicate": "Un fichier identique à celui-ci ([[:$1]]) a déjà été supprimé. \nVous devriez vérifier le journal des suppressions de ce fichier avant de l'importer à nouveau.",
"file-deleted-duplicate-notitle": "Un fichier identique à ce fichier a déjà été supprimé ainsi que le titre. \nVous devriez demander à quelqu'un la possibilité de vérifier le journal de ce fichier supprimé afin d'examiner la situation avant de l'importer à nouveau.",
"rollbacklinkcount-morethan": "révoquer plus de $1 {{PLURAL:$1|modification|modifications}}",
"rollbackfailed": "La révocation a échoué",
"rollback-missingparam": "Paramètres nécessaires à la demande manquants.",
- "rollback-missingrevision": "Impossible de charger les données de correction.",
+ "rollback-missingrevision": "Impossible de charger les données de la version.",
"cantrollback": "Impossible de révoquer la modification ;\nle dernier contributeur est le seul auteur de cette page.",
"alreadyrolled": "Impossible de révoquer la dernière modification de la page « [[:$1]] » effectuée par [[User:$2|$2]] ([[User talk:$2|Discuter]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]) ;\nquelqu'un d'autre a déjà modifié ou révoqué la page.\n\nLa dernière modification de la page a été effectuée par [[User:$3|$3]] ([[User talk:$3|Discuter]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
"editcomment": "Le résumé de la modification était : <em>$1</em>.",
"pageinfo-article-id": "Numéro de la page",
"pageinfo-language": "Langue du contenu de la page",
"pageinfo-content-model": "Modèle de contenu de la page",
+ "pageinfo-content-model-change": "modifier",
"pageinfo-robot-policy": "Indexation par robots",
"pageinfo-robot-index": "Autorisée",
"pageinfo-robot-noindex": "Interdite",
"pageinfo-magic-words": "{{PLURAL:$1|Mot magique|Mots magiques}} ($1)",
"pageinfo-hidden-categories": "{{PLURAL:$1|Catégorie cachée|Catégories cachées}} ($1)",
"pageinfo-templates": "{{PLURAL:$1|Modèle inclu|Modèles inclus}} ($1)",
- "pageinfo-transclusions": "{{PLURAL:$1|Page dans laquelle|Pages dans lesquelles}} elle est incluse ($1)",
+ "pageinfo-transclusions": "{{PLURAL:$1|Page dans laquelle|Pages dans lesquelles}} cette page est incluse ($1)",
"pageinfo-toolboxlink": "Information sur la page",
"pageinfo-redirectsto": "Rediriger vers",
"pageinfo-redirectsto-info": "info",
- "pageinfo-contentpage": "Compté comme page de contenu",
+ "pageinfo-contentpage": "Comptée comme page de contenu",
"pageinfo-contentpage-yes": "Oui",
- "pageinfo-protect-cascading": "Les protections sont déduites d'ici",
+ "pageinfo-protect-cascading": "Les protections sont déduites à partir d'ici",
"pageinfo-protect-cascading-yes": "Oui",
"pageinfo-protect-cascading-from": "Les protections sont déduites depuis",
"pageinfo-category-info": "Informations sur la catégorie",
"filedelete-archive-read-only": "Le dossier d'archivage « $1 » n'est pas modifiable par le serveur.",
"previousdiff": "← Modification précédente",
"nextdiff": "Modification suivante →",
- "mediawarning": "'''Attention :''' ce type de fichier peut contenir du code malveillant.\nSi vous l'exécutez, votre système peut être compromis.",
- "imagemaxsize": "Taille maximale des images :<br />''(pour les pages de description de fichier)''",
+ "mediawarning": "<strong>Attention :</strong> ce type de fichier peut contenir du code malveillant.\nSi vous l'exécutez, votre système peut être compromis.",
+ "imagemaxsize": "Taille maximale des images :<br /><em>(pour les pages de description de fichier)</em>",
"thumbsize": "Taille de la miniature :",
"widthheight": "$1 × $2",
"widthheightpage": "$1 × $2, $3 page{{PLURAL:$3||s}}",
"file-info-size-pages": "$1 × $2 pixels, taille de fichier : $3, type MIME : $4, $5 page{{PLURAL:$5||s}}",
"file-nohires": "Pas de plus haute résolution disponible.",
"svg-long-desc": "Fichier SVG, résolution de $1 × $2 pixels, taille : $3",
- "svg-long-desc-animated": "Fichier SVG animé, taille $1 × $2 pixels, taille du fichier : $3",
+ "svg-long-desc-animated": "Fichier SVG animé, résolution $1 × $2 pixels, taille du fichier : $3",
"svg-long-error": "Fichier SVG non valide : $1",
"show-big-image": "Fichier d'origine",
"show-big-image-preview": "Taille de cet aperçu : $1.",
"saturday-at": "Samedi à $1",
"sunday-at": "Dimanche à $1",
"yesterday-at": "Hier à $1",
- "bad_image_list": "Le format est le suivant :\n\nSeules les listes d’énumération (commençant par *) sont prises en compte. Le premier lien d’une ligne doit être celui d’une mauvaise image.\nLes autres liens sur la même ligne sont considérés comme des exceptions, par exemple des pages sur lesquelles l’image peut apparaître.",
+ "bad_image_list": "Le format est le suivant :\n\nSeules les listes d’énumération (commençant par *) sont prises en compte. \nLe premier lien d’une ligne doit être celui d’une mauvaise image.\nLes autres liens sur la même ligne sont considérés comme des exceptions, par exemple des pages sur lesquelles l’image peut apparaître.",
"variantname-ku-arab": "ku-arab",
"variantname-ku-latn": "ku-latn",
"variantname-tg-cyrl": "tg-cyrl",
"exif-yresolution": "Résolution verticale",
"exif-stripoffsets": "Emplacement des données de l'image",
"exif-rowsperstrip": "Nombre de lignes par bande",
- "exif-stripbytecounts": "Taille en octets par bande",
+ "exif-stripbytecounts": "Taille en octets par bande compressée",
"exif-jpeginterchangeformat": "Position du SOI JPEG",
"exif-jpeginterchangeformatlength": "Taille en octets des données JPEG",
"exif-whitepoint": "Chromaticité du point blanc",
"exif-primarychromaticities": "Chromaticité des primaires",
"exif-ycbcrcoefficients": "Coefficients YCbCr",
- "exif-referenceblackwhite": "Valeurs de référence noir et blanc",
- "exif-datetime": "Date de modification",
- "exif-imagedescription": "Description de l'image",
- "exif-make": "Fabricant de l'appareil",
- "exif-model": "Modèle de l'appareil",
+ "exif-referenceblackwhite": "Valeurs des couples noir et blanc de référence",
+ "exif-datetime": "Date de modification du fichier",
+ "exif-imagedescription": "Titre de l'image",
+ "exif-make": "Fabricant de l'appareil photo",
+ "exif-model": "Modèle de l'appareil photo",
"exif-software": "Logiciel utilisé",
"exif-artist": "Auteur",
"exif-copyright": "Détenteur du droit d'auteur",
"exif-exifversion": "Version EXIF",
- "exif-flashpixversion": "Version FlashPix",
+ "exif-flashpixversion": "Version FlashPix supportée",
"exif-colorspace": "Espace colorimétrique",
"exif-componentsconfiguration": "Signification de chaque composante",
"exif-compressedbitsperpixel": "Mode de compression de l'image",
"seconds-ago": "$1 {{PLURAL:$1|સેકંડ|સેકંડો}} ago",
"monday-at": "$1 પર સોમવાર",
"tuesday-at": "$1 પર મંગળવાર",
- "wednesday-at": "$1 પર બુધવાર",
+ "wednesday-at": "બુધવારે $1 વાગ્યે",
"thursday-at": "$1 પર ગુરુવાર",
"friday-at": "$1 પર શુક્રવાર",
"saturday-at": "$1 પર શનિવાર",
"createacct-yourpasswordagain-ph": "יש להקליד את הסיסמה שנית",
"userlogin-remembermypassword": "לזכור שנכנסתי",
"userlogin-signwithsecure": "שימוש בחיבור מאובטח",
+ "cannotlogin-title": "לא ניתן להיכנס לחשבון",
+ "cannotlogin-text": "הכניסה לחשבון אינה אפשרית.",
"cannotloginnow-title": "לא ניתן להיכנס עכשיו",
"cannotloginnow-text": "הכניסה אינה אפשרית בעת שימוש ב{{GRAMMAR:תחילית|$1}}.",
+ "cannotcreateaccount-title": "לא ניתן ליצור חשבונות",
+ "cannotcreateaccount-text": "יצירת חשבונות באופן ישיר אינה מותרת באתר זה.",
"yourdomainname": "המתחם שלך:",
"password-change-forbidden": "אין באפשרותך לשנות סיסמאות באתר זה.",
"externaldberror": "אירעה שגיאת אימות בבסיס הנתונים, או שאינך מורשה לעדכן את החשבון החיצוני שלך.",
"botpasswords-updated-body": "ססמת הבוט עבור בוט בשם \"$1\" של המשתמש \"$2\" עודכנה.",
"botpasswords-deleted-title": "ססמת הבוט נמחקה",
"botpasswords-deleted-body": "ססמת הבוט עבור בוט בשם \"$1\" של המשתמש \"$2\" נמחקה.",
- "botpasswords-newpassword": "×\94סס×\9e×\94 ×\94×\97×\93ש×\94 ×\9c×\9b× ×\99ס×\94 <strong>$1</strong> ×\94×\99×\90 <strong>$2</strong>. <em>× ×\90 ×\9cש×\9e×\95ר ×\9e×\99×\93×¢ ×\96×\94 ×\9cצ×\95ר×\9a ×¢×\99×\95×\9f עת×\99×\93×\99.</em>",
+ "botpasswords-newpassword": "×\94ס×\99ס×\9e×\94 ×\94×\97×\93ש×\94 ×\9c×\9b× ×\99ס×\94 ×\9c×\97ש×\91×\95×\9f <strong>$1</strong> ×\94×\99×\90 <strong>$2</strong>. <em>× ×\90 ×\9cש×\9e×\95ר ×\9e×\99×\93×¢ ×\96×\94 ×\9cצ×\95ר×\9a ×¢×\99×\95×\9f עת×\99×\93×\99.</em> <br> (×¢×\91×\95ר ×\91×\95×\98×\99×\9d ×\99×©× ×\99×\9d ש×\93×\95רש×\99×\9d שש×\9d ×\94×\9eשת×\9eש ×\91×\9b× ×\99ס×\94 ×\9c×\97ש×\91×\95×\9f ×\99×\94×\99×\94 ×\96×\94×\94 ×\9cש×\9d ×\94×\9eשת×\9eש ש×\90×\99ת×\95 ×\94×\9d ×\99פע×\9c×\95, × ×\99ת×\9f ×\9c×\94שת×\9eש ×\92×\9d ×\91ש×\9d ×\94×\9eשת×\9eש <strong>$3</strong> ×¢×\9d ×\94ס×\99ס×\9e×\94 <strong>$4</strong>.)",
"botpasswords-no-provider": "BotPasswordsSessionProvider אינו זמין.",
"botpasswords-restriction-failed": "כניסה זו נמנעה בשל הגבלות על ססמאות בוט.",
"botpasswords-invalid-name": "שם המשתמש שניתן אינו מכיל את תו הפרדת ססמאות הבוט (\"$1\").",
"invalid-content-data": "מידע שגוי על התוכן",
"content-not-allowed-here": "תוכן מסוג \"$1\" אינו מותר בדף [[$2]]",
"editwarning-warning": "עזיבת הדף הזה עלולה לגרום לך לאבד את כל השינויים שביצעת.\nאם יש לך חשבון באתר, באפשרותך לבטל את האזהרה הזאת בחלק \"{{int:prefs-editing}}\" שבהעדפות שלך.",
+ "editpage-invalidcontentmodel-title": "מודל התוכן אינו נתמך",
+ "editpage-invalidcontentmodel-text": "מודל התוכן \"$1\" אינו נתמך.",
"editpage-notsupportedcontentformat-title": "סוג התוכן אינו נתמך",
"editpage-notsupportedcontentformat-text": "תוכן מסוג $1 אינו נתמך על־ידי מודל התוכן $2.",
"content-model-wikitext": "קוד ויקי",
"pageinfo-article-id": "מזהה הדף",
"pageinfo-language": "שפת התוכן של הדף",
"pageinfo-content-model": "מודל התוכן של הדף",
+ "pageinfo-content-model-change": "שינוי",
"pageinfo-robot-policy": "איסוף על־ידי רובוטים של מנועי חיפוש",
"pageinfo-robot-index": "מותר",
"pageinfo-robot-noindex": "אסור",
"tag-filter": "מסנן [[Special:Tags|תגיות]]:",
"tag-filter-submit": "סינון",
"tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|תגית|תגיות}}]]: $2)",
+ "tag-mw-contentmodelchange": "שינוי מודל התוכן",
+ "tag-mw-contentmodelchange-description": "עריכות ש[https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel משנות את מודל התוכן] של דף",
"tags-title": "תגיות",
"tags-intro": "דף זה מכיל רשימה של תגיות שהתוכנה יכולה לסמן איתן עריכה, ומשמעויותיהן.",
"tags-tag": "שם התגית",
"tags-actions-header": "פעולות",
"tags-active-yes": "כן",
"tags-active-no": "לא",
- "tags-source-extension": "×\94×\95×\92×\93ר ×¢×\9cÖ¾×\99×\93×\99 ×\94ר×\97×\91ה",
+ "tags-source-extension": "×\94×\95×\92×\93ר ×¢×\9cÖ¾×\99×\93×\99 ×\94ת×\95×\9b× ה",
"tags-source-manual": "מוחל באופן ידני על־ידי משתמשים ובוטים",
"tags-source-none": "אינו בשימוש כעת",
"tags-edit": "עריכה",
"createacct-yourpasswordagain-ph": "Írd be a jelszót újra",
"userlogin-remembermypassword": "Maradjak bejelentkezve",
"userlogin-signwithsecure": "Biztonságos kapcsolat használata",
+ "cannotlogin-title": "A bejelentkezés nem lehetséges.",
+ "cannotlogin-text": "A bejelentkezés nem lehetséges.",
"cannotloginnow-title": "Nem lehet most bejelentkezni",
"cannotloginnow-text": "A bejelentkezés nem lehetséges $1 használatakor.",
+ "cannotcreateaccount-title": "Felhasználói fiók létrehozása sikertelen",
+ "cannotcreateaccount-text": "Közvetlen fiók létrehozása nem engedélyezett ezen a wikin.",
"yourdomainname": "A domainneved:",
"password-change-forbidden": "Nem módosíthatod a jelszót ezen a wikin.",
"externaldberror": "Hiba történt a külső adatbázis hitelesítése közben, vagy nem vagy jogosult a külső fiókod frissítésére.",
"pageinfo-article-id": "Lapazonosító",
"pageinfo-language": "Laptartalom nyelve",
"pageinfo-content-model": "Oldal tartalom modell",
+ "pageinfo-content-model-change": "módosítás",
"pageinfo-robot-policy": "Indexelés robottal",
"pageinfo-robot-index": "Engedélyezett",
"pageinfo-robot-noindex": "Nem engedélyezett",
"yourpasswordagain": "Repete contrasigno:",
"createacct-yourpasswordagain": "Confirma contrasigno",
"createacct-yourpasswordagain-ph": "Repete le contrasigno",
- "remembermypassword": "Memorar mi contrasigno in iste navigator (pro un maximo de $1 {{PLURAL:$1|die|dies}})",
"userlogin-remembermypassword": "Mantener mi session aperte",
"userlogin-signwithsecure": "Usar un connexion secur",
+ "cannotlogin-title": "Impossibile aperir session",
+ "cannotlogin-text": "Non es possibile aperir un session.",
"cannotloginnow-title": "Impossibile aperir session ora",
"cannotloginnow-text": "Non es possibile aperir un session usante $1.",
+ "cannotcreateaccount-title": "Impossibile crear contos",
+ "cannotcreateaccount-text": "Le creation directe de contos non es activate in iste wiki.",
"yourdomainname": "Tu dominio:",
"password-change-forbidden": "Non es possibile cambiar le contrasigno in iste wiki.",
"externaldberror": "O il occurreva un error in le base de datos de authentication, o tu non ha le autorisation de actualisar tu conto externe.",
"grant-group-high-volume": "Exequer actiones in massa",
"grant-group-customization": "Personalisation e perferentias",
"grant-group-administration": "Exequer actiones administrative",
+ "grant-group-private-information": "Acceder a tu datos private",
"grant-group-other": "Activitates diverse",
"grant-blockusers": "Blocar e disblocar usatores",
"grant-createaccount": "Crear contos",
"grant-highvolume": "Modification in massa",
"grant-oversight": "Celar usatores e supprimer versiones",
"grant-patrol": "Patruliar cambiamentos a paginas",
+ "grant-privateinfo": "Acceder a information private",
"grant-protect": "Proteger e disproteger paginas",
"grant-rollback": "Revocar cambiamentos a paginas",
"grant-sendemail": "Inviar e-mail a altere usatores",
"file-thumbnail-no": "Le nomine del file comencia con <strong>$1</strong>.\nIllo pare esser un imagine a grandor reducite ''(miniatura)''.\nSi tu possede iste imagine in plen resolution, incarga lo, alteremente cambia le nomine del file per favor.",
"fileexists-forbidden": "Un file con iste nomine existe ja, e non pote esser superscribite.\nSi tu vole ancora incargar iste file, per favor retorna e usa un nove nomine. [[File:$1|thumb|center|$1]]",
"fileexists-shared-forbidden": "Un file con iste nomine existe ja in le repositorio de files commun.\nSi tu vole totevia incargar iste file, per favor retorna e usa un nove nomine. [[File:$1|thumb|center|$1]]",
+ "fileexists-no-change": "Le file incargate es un copia exacte del version actual de <strong>[[:$1]]</strong>.",
+ "fileexists-duplicate-version": "Le file incargate es un copia exacte de {{PLURAL:$2|un version|versiones}} precedente de <strong>[[:$1]]</strong>.",
"file-exists-duplicate": "Iste file es un duplicato del sequente {{PLURAL:$1|file|files}}:",
"file-deleted-duplicate": "Un file identic a iste file ([[:$1]]) esseva ja delite anteriormente. Tu deberea verificar le registro de deletiones concernente iste file ante de re-incargar lo.",
"file-deleted-duplicate-notitle": "Un file identic a iste file ha essite delite anteriormente, e le titulo ha essite supprimite. Tu deberea demandar a un persona con le privilegio de vider datos de files supprimite a examinar le situation ante de incargar lo de novo.",
"filerevert-submit": "Reverter",
"filerevert-success": "'''[[Media:$1|$1]]''' ha essite revertite al [$4 version del $3 a $2].",
"filerevert-badversion": "Non existe un version local anterior de iste file con le data e hora providite.",
+ "filerevert-identical": "Le version actual del file es jam identic al file seligite.",
"filedelete": "Deler $1",
"filedelete-legend": "Deler file",
"filedelete-intro": "Tu es super le puncto de deler le file '''[[Media:$1|$1]]''' con tote su historia.",
"rollbacklinkcount-morethan": "revocar plus de $1 {{PLURAL:$1|modification|modificationes}}",
"rollbackfailed": "Revocation fallite",
"rollback-missingparam": "Manca parametros obligatori in le requesta.",
+ "rollback-missingrevision": "Impossibile cargar le datos del version.",
"cantrollback": "Impossibile revocar le modification;\nle ultime contributor es le sol autor de iste pagina.",
"alreadyrolled": "Non pote revocar le ultime modification de [[:$1]] per [[User:$2|$2]] ([[User talk:$2|discussion]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);\nun altere persona ha ja modificate o revocate le pagina.\n\nLe ultime modification esseva facite per [[User:$3|$3]] ([[User talk:$3|discussion]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
"editcomment": "Le summario del modification esseva: <em>$1</em>.",
"pageinfo-article-id": "ID del pagina",
"pageinfo-language": "Lingua del contento del pagina",
"pageinfo-content-model": "Modello de contento de pagina",
+ "pageinfo-content-model-change": "cambiar",
"pageinfo-robot-policy": "Indexation per robots",
"pageinfo-robot-index": "Permittite",
"pageinfo-robot-noindex": "Non permittite",
"linkaccounts-submit": "Ligar contos",
"unlinkaccounts": "Disligar contos",
"unlinkaccounts-success": "Le conto ha essite disligate.",
- "authenticationdatachange-ignored": "Le cambiamento del datos de authentication non ha succedite. Pote esser que nulle fornitor ha essite configurate?"
+ "authenticationdatachange-ignored": "Le cambiamento del datos de authentication non ha succedite. Pote esser que nulle fornitor ha essite configurate?",
+ "userjsispublic": "Nota ben: Subpaginas JavaScript non debe continer datos confidential perque altere usatores pote vider los.",
+ "usercssispublic": "Nota ben: Subpaginas CSS non debe continer datos confidential perque altere usatores pote vider los."
}
"undelete_short": "Isubli ti pannakaikkat {{PLURAL:$1|ti maysa a naurnos|dagiti $1 a naurnos}}",
"viewdeleted_short": "Kitaen {{PLURAL:$1|ti maysa a naikkat a naurnos|dagiti $1 a naikkat a naurnos}}",
"protect": "Salakniban",
- "protect_change": "sukatan",
+ "protect_change": "baliwan",
"protectthispage": "Salakniban daytoy a panid",
"unprotect": "Sukatan ti salaknib",
"unprotectthispage": "Sukatan ti salaknib daytoy a panid",
"createacct-yourpasswordagain-ph": "Ikabil manen ti kontrasenias",
"userlogin-remembermypassword": "Taginayonennak nga iserrek",
"userlogin-signwithsecure": "Usaren ti natalged a koneksion",
+ "cannotlogin-title": "Saan a makastrek",
+ "cannotlogin-text": "Saan a mabalin ti panagserrek.",
"cannotloginnow-title": "Saan a mabalin itan iti sumrek",
"cannotloginnow-text": "Saan a mabalin ti sumrek no agus-usar iti $1.",
+ "cannotcreateaccount-title": "Saan a makapartuat kadagiti pakabilangan",
+ "cannotcreateaccount-text": "Saan a napakabaelan ti dagus a panagpartuat iti pakabilangan iti daytoy a wiki.",
"yourdomainname": "Ti bukodmo a dominio:",
"password-change-forbidden": "Saanmo a mabaliwan dagiti kontrasenias iti daytoy a wiki.",
"externaldberror": "Mabalin nga adda biddut iti pannakapasingked ti database wenno saanka a mapalubosan a mangpabaro ti akinruar a pakabilangam.",
"botpasswords-updated-body": "Napabaro ti kontrasenias ti bot para iti nagan ti bot iti \"$1\" ni agar-aramat \"$2\".",
"botpasswords-deleted-title": "Naikkat ti kontrasenias ti bot",
"botpasswords-deleted-body": "Naikkat ti kontrasenias ti bot para iti nagan ti bot iti \"$1\" ni agar-aramat \"$2\".",
- "botpasswords-newpassword": "Ti baro a kontrasenias iti panagserrek iti <strong>$1</strong> ket <strong>$2</strong>. <em>Pangngaasi nga irekord daytoy para iti masakbayan a reperensia.</em>",
+ "botpasswords-newpassword": "Ti baro a kontrasenias iti panagserrek iti <strong>$1</strong> ket <strong>$2</strong>. <em>Pangngaasi nga irekord daytoy para iti masakbayan a reperensia.</em> <br> (Para kadagiti daan a bot a makasapul iti nagan iti panagserrek a kapada koma ti kanungpalan a nagan ti agar-aramat, mabalinmo pay ti agusar ti <strong>$3</strong> a kas nagan ti <strong>$4</strong> a kas kontrasenias.)",
"botpasswords-no-provider": "Saan a magun-od ti BotPasswordsSessionProvider.",
"botpasswords-restriction-failed": "Ti panangigawid ti kontrasenias ti bot ket nangpawil iti daytoy a panagserrek.",
"botpasswords-invalid-name": "Ti naibaga a nagan ti agar-aramat ket saan nga aglaon iti panangisina ti kontrasenias ti bot (\"$1\").",
"invalid-content-data": "Imbalido a datos ti linaon",
"content-not-allowed-here": "Ti \"$1\" a linaon ket saan a maipalubos iti panid ti [[$2]]",
"editwarning-warning": "Ti ipapanaw iti daytoy a panid ket makapataud ti pannakapukaw kadagiti ania man a binalbaliwam.\nNo nakastrekka, mabalinmo nga ibaldado daytoy a ballaag iti \"{{int:prefs-editing}}\" a seksion kadagiti kakaykayatam.",
+ "editpage-invalidcontentmodel-title": "Saan a masuportaran ti modelo ti linaon",
+ "editpage-invalidcontentmodel-text": "Saan a nasuportaran ti modelo ti linaon ti \"$1\".",
"editpage-notsupportedcontentformat-title": "Ti pormat ti linaon ket saan a nasuportaran",
"editpage-notsupportedcontentformat-text": "Ti pormat ti linaon ti $1 ket saan a nasuportaran babaen ti modelo ti linaon ti $2.",
"content-model-wikitext": "wikitext",
"file-thumbnail-no": "Ti nagan ti papeles ket mangrugi iti <strong>$1</strong>.\nKasla ti ladawan a napabassit ti kadakkel <em>(thumbnail)</em>.\nNo addaanka iti daytoy a ladawan iti napno a resolusion ikargam dayta, no saan pangngaasi a sukatam ti nagan ti papeles.",
"fileexists-forbidden": "Daytoy a nagan ti papeles ket addan, ken saan a mabalin a masuratan manen.\nNo kayatmo pay latta nga ikarga ti papelesmo, pangngaasi nga agsublika ken agusar iti baro a nagan.\n[[File:$1|thumb|center|$1]]",
"fileexists-shared-forbidden": "Ti papeles iti daytoy a nagan ket addan iti pagbingayan a repositorio ti papeles.\nNo kayatmo pay latta nga ikarga ti papeles, pangngaasi nga agsublika ken agusar iti baro a nagan.\n[[File:$1|thumb|center|$1]]",
+ "fileexists-no-change": "Ti karga ket maysa nga eksakto a duplikado ti agdama a bersion ti <strong>[[:$1]]</strong>.",
+ "fileexists-duplicate-version": "Ti karga ket maysa nga eksakto a duplikado {{PLURAL:$2|ti maysa a daan a bersion|dagiti daan a bersion}} ti <strong>[[:$1]]</strong>.",
"file-exists-duplicate": "Daytoy a papeles ket duplikado kadagiti sumaganad a {{PLURAL:$1|papeles|pappapeles}}:",
"file-deleted-duplicate": "Ti papeles a kapadpada ti papeles a ([[:$1]]) ket dati a naikkat.\nKitaem koma ti pakasaritaan a pannakaikkat ti papeles sakbay a mangirugika a mangikarga manen.",
"file-deleted-duplicate-notitle": "Ti papales a kapada iti daytoy a papeles ket dati a naikkat, ken nalapdan ti titulo.\nNasken nga agdamagka iti sabali nga addaan iti abilidad a mangrepaso ti nalapdan a datos ti papeles tapno marepaso ti kasasaad sakbay a mapan nga agikarga manen iti daytoy.",
"filerevert-submit": "Isubli",
"filerevert-success": "Ti <strong>[[Media:$1|$1]]</strong> ket naipasubli iti [$4 bersion manipud idi $3, $2].",
"filerevert-badversion": "Awan ti dati a lokal a bersion iti daytoy a papeles a naited ti dayta nga oras ken petsa.",
+ "filerevert-identical": "Ti agdama a bersion ti papeles ket kapadan iti maysa a napili.",
"filedelete": "Ikkaten ti $1",
"filedelete-legend": "Ikkaten ti papeles",
"filedelete-intro": "Mangrugrugika nga agikkat ti <strong>[[Media:$1|$1]]</strong> ken mairaman amin a pakasaritaanna.",
"rollbacklinkcount-morethan": "agisubli ti ad-adu ngem $1 {{PLURAL:$1|nga inurnos|nga inur-urnos}}",
"rollbackfailed": "Napaay ti panangisubli",
"rollback-missingparam": "Awan dagiti nasken a parametro iti kiddaw.",
+ "rollback-missingrevision": "Saan a maikarga ti datos ti rebision.",
"cantrollback": "Saan a maisubli ti panagurnos;\nti naudi a nakaaramid ket iti laeng nagsurat iti daytoy a panid.",
"alreadyrolled": "Saan a maipasubli ti kinaudi a panagurnos iti [[:$1]] babaen ni [[User:$2|$2]] ([[User talk:$2|tungtungan]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);\nadda sabali a naurnos wenno nagipasubli ti panid.\n\nTi kinaudi a panagurnos ti daytoy a panid ket babaen ni [[User:$3|$3]] ([[User talk:$3|tungtungan]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
"editcomment": "Ti pakabuklan idi ti panagurnos ket: <em>$1</em>.",
"pageinfo-article-id": "ID ti panid",
"pageinfo-language": "Pagsasao ti naglaon a panid",
"pageinfo-content-model": "Modelo ti linaon ti panid",
+ "pageinfo-content-model-change": "baliwan",
"pageinfo-robot-policy": "Panagpasurot babaen dagiti robot",
"pageinfo-robot-index": "Maipalubos",
"pageinfo-robot-noindex": "Saan a maipalubos",
"confirmemail_body_set": "Addaan, baka sika met laeng, manipud ti IP a pagtaengan ti $1,\nket nangikabil ti esurat a pagtaengan ti pakabilangan ti \"$2\" iti daytoy a pagtaengan idiay {{SITENAME}}\n\nTapno mapasingkedan daytoy a pakabilangan ket agpayso a kukuam ken \npakabaelan dagiti esurat a langa idiay {{SITENAME}}, lukatam daytoy a silpo idiay pabasabasam:\n\n$3\n\nNo daytoy a pakabilangan ket *saanmo* a kukua, surutem daytoy a silpo\ntapno ukasen ti panagpasingked ti esurat a pagtaengan:\n\n$5\n\nDaytoy a panagpasingked ti kodigo ket agpaso intono $4.",
"confirmemail_invalidated": "Naukas ti pammasingked ti esurat a pagtaengam",
"invalidateemail": "Ukasen ti pammasingked ti esurat",
+ "notificationemail_subject_changed": "Nabaliwanen ti nairehistro nga adres ti esurat ti {{SITENAME}}",
+ "notificationemail_subject_removed": "Naikkaten ti nairehistro nga adres ti esurat ti {{SITENAME}}",
+ "notificationemail_body_changed": "Adda maysa a tao, mabalin a sika, manipud iti adres ti IP ti $1,\nket nangbaliw ti adres ti esurat ti pakabilangan ti \"$2\" iti \"$3\" iti {{SITENAME}}.\n\nNo saan a sika daytoy, dagus a kontaken ti administrador ti sitio.",
+ "notificationemail_body_removed": "Adda maysa a tao, mabalin a sika, manipud iti adres ti IP ti $1,\nket nangikkat ti adres ti esurat ti pakabilangan ti \"$2\" iti {{SITENAME}}.\n\nNo saan a sika daytoy, dagus a kontaken ti administrador ti sitio.",
"scarytranscludedisabled": "[Nabaldado ti Interwiki panagiraman]",
"scarytranscludefailed": "[Napaay ti panagala ti plantilia para iti $1]",
"scarytranscludefailed-httpstatus": "[Napaay ti panagala ti plantilia para iti $1: HTTP $2]",
"tag-filter": "Sagat ti [[Special:Tags|etiketa]]:",
"tag-filter-submit": "Sagat",
"tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Etiketa|Et-etiketa}}]]: $2)",
+ "tag-mw-contentmodelchange": "panagbaliw ti modelo ti linaon",
+ "tag-mw-contentmodelchange-description": "Dagiti panagurnos a [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel mangbaliw ti modelo ti linaon] ti panid",
"tags-title": "Dagiti etiketa",
"tags-intro": "Daytoy a panid ket ilistana dagiti etiketa a mablin nga usaren ti sopwer a mangmarka iti inurnos, ken dagiti kaibuksilanda.",
"tags-tag": "Nagan ti etiketa",
"tags-actions-header": "Dagiti aramid",
"tags-active-yes": "Wen",
"tags-active-no": "Saan",
- "tags-source-extension": "Naipalawag babaen ti maysa a pagpaatiddog",
+ "tags-source-extension": "Naipalawag babaen ti sopwer",
"tags-source-manual": "Manual a naipakat babaen dagiti agar-aramat ken dagiti bot",
"tags-source-none": "Saan a maus-usar",
"tags-edit": "urnosen",
"feedback-useragent": "Ahente ti agar-aramat:",
"searchsuggest-search": "Biruken",
"searchsuggest-containing": "naglaon ti...",
+ "api-error-autoblocked": "Automatiko a naserraan ti IP nga adresmo, gapu ta inusar babaen ti naseraan nga agar-aramat.",
"api-error-badaccess-groups": "Saanka mapalubosan nga agikarga kadagiti papeles iti daytoy a wiki.",
"api-error-badtoken": "Akin-uneg a biddut: Dakes a tandaan.",
"api-error-blocked": "Naserraankan manipud iti panagurnos.",
"linkaccounts-submit": "Isilpo dagiti pakabilangan",
"unlinkaccounts": "Ikkaten ti silpo dagiti pakabilangan",
"unlinkaccounts-success": "Ti pakabilangan ket naikkat iti pannakaisilpo.",
- "authenticationdatachange-ignored": "Saan a natengngel ti panagbaliw ti datos ti pammasingked. Mabalin nga awan ti nakompigura a mangited?"
+ "authenticationdatachange-ignored": "Saan a natengngel ti panagbaliw ti datos ti pammasingked. Mabalin nga awan ti nakompigura a mangited?",
+ "userjsispublic": "Pangngaasi a laglagipen: Dagiti subpanid ti JavaScript ket nasken a saan nga aglaon iti datos a nailemed gapu ta makita dagitoy babaen dagiti sabali nga agar-aramat.",
+ "usercssispublic": "Pangngaasi a laglagipen: Dagiti subpanid ti CSS ket nasken a saan nga aglaon iti datos a nailemed gapu ta makita dagitoy babaen dagiti sabali nga agar-aramat."
}
"botpasswords-updated-body": "La password per il bot di nome \"$1\" dell'utente \"$2\" è stata aggiornata.",
"botpasswords-deleted-title": "Password bot cancellata",
"botpasswords-deleted-body": "La password per il bot di nome \"$1\" dell'utente \"$2\" è stata cancellata.",
- "botpasswords-newpassword": "La nuova password per accedere con <strong>$1</strong> è <strong>$2</strong>. <em>Registratelo per consultazioni future.</em>",
+ "botpasswords-newpassword": "La nuova password per accedere con <strong>$1</strong> è <strong>$2</strong>. <em>Registratelo per consultazioni future.</em> <br> (Per i vecchi bot che richiedono che il nome per accedere sia lo stesso del nome utente, puoi utilizzare <strong>$3</strong> come nome utente e <strong>$4</strong> come password.)",
"botpasswords-no-provider": "BotPasswordsSessionProvider non è disponibile.",
"botpasswords-restriction-failed": "Le restrizioni di password bot impediscono questo accesso.",
"botpasswords-invalid-name": "Il nome utente indicato non contiene il separatore per password bot (\"$1\").",
"invalid-content-data": "Dati contenuti non validi",
"content-not-allowed-here": "Contenuto in \"$1\" non consentito nella pagina [[$2]]",
"editwarning-warning": "Lasciare questa pagina potrebbe causare la perdita di tutte le modifiche fatte.\nSe hai effettuato l'accesso, puoi disattivare questo avviso nella sezione \"{{int:prefs-editing}}\" delle tue preferenze.",
+ "editpage-invalidcontentmodel-title": "Modello di contenuto non supportato",
+ "editpage-invalidcontentmodel-text": "Il modello di contenuto \"$1\" non è supportato.",
"editpage-notsupportedcontentformat-title": "Formato contenuto non supportato",
"editpage-notsupportedcontentformat-text": "Il formato del contenuto $1 non è supportato dal modello di contenuto $2.",
"content-model-wikitext": "wikitesto",
"tooltip-ca-undelete": "Ripristina la pagina com'era prima della cancellazione",
"tooltip-ca-move": "Sposta questa pagina (cambia titolo)",
"tooltip-ca-watch": "Aggiungi questa pagina alla tua lista degli osservati speciali",
- "tooltip-ca-unwatch": "Elimina questa pagina dalla tua lista degli osservati speciali",
+ "tooltip-ca-unwatch": "Rimuovi questa pagina dalla tua lista degli osservati speciali",
"tooltip-search": "Cerca all'interno di {{SITENAME}}",
"tooltip-search-go": "Vai a una pagina con il titolo indicato, se esiste",
"tooltip-search-fulltext": "Cerca il testo indicato nelle pagine",
"pageinfo-article-id": "ID della pagina",
"pageinfo-language": "Lingua del contenuto della pagina",
"pageinfo-content-model": "Modello del contenuto della pagina",
+ "pageinfo-content-model-change": "cambia",
"pageinfo-robot-policy": "Indicizzazione per i robot",
"pageinfo-robot-index": "Consentito",
"pageinfo-robot-noindex": "Non consentito",
"confirm-watch-button": "OK",
"confirm-watch-top": "Aggiungi questa pagina alla tua lista degli osservati speciali?",
"confirm-unwatch-button": "OK",
- "confirm-unwatch-top": "Elimina questa pagina dalla tua lista degli osservati speciali?",
+ "confirm-unwatch-top": "Rimuovere questa pagina dalla tua lista degli osservati speciali?",
"confirm-rollback-button": "OK",
"confirm-rollback-top": "Ripristinare le modifiche di questa pagina?",
"percent": "$1 %",
"tag-filter": "Filtra per [[Special:Tags|etichetta]]:",
"tag-filter-submit": "Filtra",
"tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Etichetta|Etichette}}]]: $2)",
+ "tag-mw-contentmodelchange": "modifica modello contenuti",
+ "tag-mw-contentmodelchange-description": "Modifiche che [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel cambiano il modello di contenuti] di una pagina",
"tags-title": "Etichette",
"tags-intro": "Questa pagina elenca le etichette che il software potrebbe associare a una modifica e il loro significato.",
"tags-tag": "Nome dell'etichetta",
"tags-actions-header": "Azioni",
"tags-active-yes": "Sì",
"tags-active-no": "No",
- "tags-source-extension": "Definito da un'estensione",
+ "tags-source-extension": "Definito dal software",
"tags-source-manual": "Applicato manualmente da utenti e bot",
"tags-source-none": "Non più in uso",
"tags-edit": "modifica",
"missingcommentheader": "'''Pangéling:''' Sampéyan durung nyadhiyakaké judhul/jejer kanggo tanggepan iki.\nYèn Sampéyan klik \"{{int:savearticle}}\" manèh, suntingan Sampéyan bakal kasimpen tanpa kuwi.",
"summary-preview": "Pratuduh tingkesan:",
"subject-preview": "Prawuryaning jejer:",
+ "previewerrortext": "Cacad dumadi nalika njajal mratuduh owahanmu.",
"blockedtitle": "Panganggo kapalangan",
"blockedtext": "<b>Asma panganggo utawa alamat IP panjenengan diblokir.</b>\n\nBlokir iki sing nglakoni $1.\nAlesané <i>$2</i>.\n\n* Diblokir wiwit: $8\n* Kadaluwarsa pemblokiran ing: $6\n* Sing arep diblokir: $7\n\nPanjenengan bisa ngubungi $1 utawa [[{{MediaWiki:Grouppage-sysop}}|pangurus liyané]] kanggo ngomongaké prakara iki.\n\nPanjenengan ora bisa nggunakaké fitur 'Kirim layang é-mail panganggo iki' kejaba panjenengan wis nglebokaké alamat é-mail sing sah ing [[Special:Preferences|prèferènsi]] panjenengan.\n\nAlamat IP panjenengan iku $3, lan ID pamblokiran iku #$5.\nTulung kabèh informasi ing ndhuwur iki disertakaké ing saben pitakon panjenengan.",
"autoblockedtext": "Alamat IP panjenangan wis diblokir minangka otomatis amerga dienggo déning panganggo liyané. Pamblokiran dilakoni déning $1 mawa alesan:\n\n:''$2''\n\n* Diblokir wiwit: $8\n* Blokir kadaluwarsa ing: $6\n* Sing dikarepaké diblokir: $7\n\nPanjenengan bisa ngubungi $1 utawa [[{{MediaWiki:Grouppage-sysop}}|pangurus liyané]] kanggo ngomongaké perkara iki.\n\nPanjenengan ora bisa nganggo fitur \"kirim e-mail panganggo iki\" kejaba panjenengan wis nglebokaké alamat e-mail sing sah ing [[Special:Preferences|prèferènsi]] panjenengan lan panjenengan wis diblokir kanggo nggunakaké.\n\nID pamblokiran panjenengan iku #$5 lan alamat IP panjenengan iku $3. Tulung sertakna informasi ing dhuwur kabèh iki saben ngajokaké pitakonan panjenengan. Matur nuwun.",
"right-markbotedits": "Tandhani besutan kawurungan minangka besutan bot",
"right-noratelimit": "Ora dipengaruhi déning wates cacahing suntingan.",
"right-import": "Impor kaca-kaca saka wiki liya",
- "right-importupload": "Impor kaca-kaca saka sawijining pangunggahan berkas",
+ "right-importupload": "Impor kaca saka unggahan barkas",
"right-patrol": "Tandhanana suntingan minangka wis dipatroli",
"right-autopatrol": "Gawé supaya suntingan-suntingan ditandhani minangka wis dipatroli",
"right-patrolmarks": "Ndeleng tandha-tandha patroli owah-owahan anyar",
"uploadnologin": "Durung mlebu log",
"uploadnologintext": "Sampéyan kudu $1 supaya bisa ngunggah berkas.",
"upload_directory_missing": "Direktori pamunggahan ($1) ora ditemokaké lan ora bisa digawé déning server wèb.",
- "upload_directory_read_only": "Dirèktori pangunggahan ($1) ora bisa ditulis déning server wèb.",
+ "upload_directory_read_only": "Dhirèktori pangunggahan ($1) ora bisa ditulis déning paladèn jaringan.",
"uploaderror": "Kaluputan pangunggahan berkas",
"upload-recreate-warning": "'''Pèngetan: Berkas mawa jeneng kuwi wis dibusak utawa disingkiraké.'''\n\nLog pambusakan lan panyingkiran saka kaca iki sumadhiya nèng kéné:",
"uploadtext": "Anggé formulir ing ngandhap punika kanggé nginggahaké gambar.\nKanggé mirsani utawi madosi gambar ingkang sampun dipununggah sakdèrèngipun pigunakaken [[Special:FileList|dhaftar berkas sing wis diunggah]], gambar ingkang dipununggah ulang ugi kadhaftar ing [[Special:Log/upload|log pangunggahan]], pambusakan ing [[Special:Log/delete|Log pambusakan]].\n\nKanggé nyertakaken gambar ing satunggiling kaca, pigunakaken pranala salah setunggal saking format ing ngandhap punika:\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Berkas.jpg]]</nowiki></code>''' kanggé migunakaken versi pepak gambar\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Berkas.png|200px|thumb|left|tèks alt]]</nowiki></code>''' kanggé migunakaken gambar wiyaripun 200 piksel ing kothak ing sisih kiwa kanthi 'tèks alt' minangka panjelasan\n* '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:Berkas.ogg]]</nowiki></code>''' kanggé nggandhèng langsung dhumateng gambar tanpi nampilaké gambar",
"upload-permitted": "Jenis berkas sing diidinaké: $1.",
"upload-preferred": "Jenis berkas sing disaranaké: $1.",
"upload-prohibited": "Jenis berkas sing dilarang: $1.",
- "uploadlogpage": "Log pangunggahan",
+ "uploadlogpage": "Log unggah",
"uploadlogpagetext": "Ing ngisor iki kapacak log pangunggahan berkas sing anyar dhéwé.\nMangga mirsani [[Special:NewFiles|galeri berkas-berkas anyar]] kanggo pratélan visual.",
"filename": "Jeneng barkas",
"filedesc": "Tingkesan",
"tooltip-watchlistedit-normal-submit": "Busak sesirah",
"tooltip-watchlistedit-raw-submit": "Anyari daptar pangawasan",
"tooltip-recreate": "Gawéa kaca iki manèh senadyan tau dibusak",
- "tooltip-upload": "Miwiti pangunggahan",
+ "tooltip-upload": "Wiwit ngunggah",
"tooltip-rollback": "Balèkaké besutan-besutan kaca iki déning sing pungkasan nyumbang sarana saklikan.",
"tooltip-undo": "\"Wurung\" mbalèkaké besutan iki lan mbukak blangko besutan sarana modhe pratuduh. Alesan kena diwuwuhaké ing babagan ringkesan.",
"tooltip-preferences-save": "Simpen préperensi",
"undelete_short": "{{PLURAL:$1|편집 한 개|편집 $1개}} 되살리기",
"viewdeleted_short": "{{PLURAL:$1|삭제된 편집 한 개|삭제된 편집 $1개}} 보기",
"protect": "보호",
- "protect_change": "ë³´í\98¸ ì\88\98ì¤\80 ë°\94꾸기",
+ "protect_change": "바꾸기",
"protectthispage": "이 문서 보호하기",
"unprotect": "보호 설정 바꾸기",
"unprotectthispage": "이 문서의 보호 설정을 바꾸기",
"createacct-yourpasswordagain-ph": "비밀번호를 다시 입력하세요",
"userlogin-remembermypassword": "로그인 상태를 유지하기",
"userlogin-signwithsecure": "보안 연결 사용",
+ "cannotlogin-title": "로그인할 수 없음",
+ "cannotlogin-text": "로그인할 수 없습니다.",
"cannotloginnow-title": "지금 로그인할 수 없습니다.",
"cannotloginnow-text": "$1 사용 중에는 로그인이 불가능합니다.",
+ "cannotcreateaccount-title": "계정을 만들 수 없습니다",
+ "cannotcreateaccount-text": "이 위키에서 직접 계정 만들기는 활성화되어 있지 않습니다.",
"yourdomainname": "도메인 이름:",
"password-change-forbidden": "이 위키에서 비밀번호를 바꿀 수 없습니다.",
"externaldberror": "인증 데이터베이스에 오류가 있거나 바깥 계정을 새로 고칠 권한이 없습니다.",
"invalid-content-data": "잘못된 내용 데이터입니다",
"content-not-allowed-here": "\"$1\" 내용은 [[$2]] 문서예 허용하지 않습니다",
"editwarning-warning": "이 페이지에서 벗어나면 저장하지 않은 바뀜이 모두 사라집니다.\n로그인을 했다면, 환경 설정의 \"{{int:prefs-editing}}\"에서 이 경고를 띄우지 않도록 설정할 수 있습니다.",
+ "editpage-invalidcontentmodel-title": "지원하지 않는 콘텐츠 모델",
+ "editpage-invalidcontentmodel-text": "\"$1\" 콘텐츠 모델은 지원되지 않습니다.",
"editpage-notsupportedcontentformat-title": "지원하지 않는 내용 형식",
"editpage-notsupportedcontentformat-text": "내용 형식 $1은(는) $2 내용 모델에서 지원하지 않습니다.",
"content-model-wikitext": "위키텍스트",
"pageinfo-article-id": "문서 ID",
"pageinfo-language": "문서 내용 언어",
"pageinfo-content-model": "문서 내용 모델",
+ "pageinfo-content-model-change": "변경",
"pageinfo-robot-policy": "로봇에 의한 색인",
"pageinfo-robot-index": "허용됨",
"pageinfo-robot-noindex": "불허됨",
"tag-filter": "[[Special:Tags|태그]] 필터:",
"tag-filter-submit": "필터",
"tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|태그}}]]: $2)",
+ "tag-mw-contentmodelchange": "콘텐츠 모델 변경",
"tags-title": "태그",
"tags-intro": "이 문서는 소프트웨어에서 편집에 대해 표시하는 태그와 그 의미를 설명하는 목록입니다.",
"tags-tag": "태그 이름",
"tags-actions-header": "동작",
"tags-active-yes": "예",
"tags-active-no": "아니오",
- "tags-source-extension": "확장 기능에 의해 정의됨",
+ "tags-source-extension": "소프트웨어에 의해 정의됨",
"tags-source-manual": "사용자나 봇에 의해 수동으로 적용됨",
"tags-source-none": "더 이상 사용하지 않음",
"tags-edit": "편집",
"createacct-yourpasswordagain-ph": "Passwuert nach eng Kéier aginn",
"userlogin-remembermypassword": "Mech ageloggt halen",
"userlogin-signwithsecure": "Eng sécher Verbindung benotzen",
+ "cannotlogin-title": "Aloggen ass net méiglech",
+ "cannotlogin-text": "Aloggen ass net méiglech.",
"cannotloginnow-title": "Aloggen ass elo net méiglech",
"cannotloginnow-text": "Aloggen ass net méiglech wann dir $1 benotzt.",
+ "cannotcreateaccount-title": "Benotzerkont kënnen net opgemaach ginn",
+ "cannotcreateaccount-text": "D'direkt Uleeë vu Benotzerkonten ass an dëser Wiki net aktivéiert.",
"yourdomainname": "Ären Domän:",
"password-change-forbidden": "Dir däerft op dëser Wiki Passwierder net änneren.",
"externaldberror": "Entweder ass e Feeler bei der externer Authentifizéierung geschitt, oder Dir däerft Ären externe Benotzerkont net aktualiséieren.",
"invalid-content-data": "Donnéeë vum Inhalt sinn net valabel",
"content-not-allowed-here": "\"$1\"-Inhalt ass op der Säit [[$2]] net erlaabt",
"editwarning-warning": "Wann Dir dës Säit verloosst kann dat dozou féieren datt Dir all Ännerungen, déi Dir gemaach hutt, verléiert.\nWann Dir ageloggt sidd, kënnt Dir dës Warnung an der Sektioun \"{{int:prefs-editing}}\" vun Ären Astellungen ausschalten.",
+ "editpage-invalidcontentmodel-title": "Modell vum Inhalt gëtt net ënnerstëtzt",
"editpage-notsupportedcontentformat-title": "Format vum Inhalt gëtt net ënnerstëtzt",
"editpage-notsupportedcontentformat-text": "De Format vum Inhalt $1 gëtt net vum Modell vum Inhalt $2 ënnerstëtzt.",
"content-model-wikitext": "Wikitext",
"pageinfo-article-id": "ID (Nummer) vun der Säit",
"pageinfo-language": "Sprooch vum Inhalt vun der Säit",
"pageinfo-content-model": "Modell vun enger Säit mat Inhalt",
+ "pageinfo-content-model-change": "änneren",
"pageinfo-robot-policy": "Indexéierung duerch Botten",
"pageinfo-robot-index": "Erlaabt",
"pageinfo-robot-noindex": "Net erlaabt",
"tag-filter": "[[Special:Tags|Markéierungs]]-Filter:",
"tag-filter-submit": "Filter",
"tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Tag|Tags}}]]: $2)",
+ "tag-mw-contentmodelchange": "Ännerung vum Modell vum Inhalt",
"tags-title": "Markéierungen",
"tags-intro": "Op dëser Säit stinn all déi Taggen, déi vun dëser Software fir Ännerungen unzeweise benotzt ginn, an hir Bedeitung.",
"tags-tag": "Numm vun der Markéierung",
"tags-actions-header": "Aktiounen",
"tags-active-yes": "Jo",
"tags-active-no": "Neen",
- "tags-source-extension": "Duerch eng Erweiderung definéiert",
+ "tags-source-extension": "Duerch d'Software definéiert",
"tags-source-manual": "Manuell vu Benotzer a vu Botten agesat",
"tags-source-none": "Gëtt net méi gebraucht",
"tags-edit": "änneren",
"tags-edit-revision-submit": "Ännerungen op {{PLURAL:$1|dës Versioun|$1 Versiounen}} uwennen",
"tags-edit-success": "D'Ännerunge goufen applizéiert.",
"tags-edit-failure": "D'Ännerunge konnten net applizéiert ginn: $1",
+ "tags-edit-nooldid-title": "Net-valabel Zilversioun",
"tags-edit-none-selected": "Sicht mindestens eng Markéierung eraus déi dir dobäisetzen oder ewechhuele wëllt.",
"comparepages": "Säite vergläichen",
"compare-page1": "Säit 1",
"tagline": "Da {{SITENAME}}",
"help": "Agiùtto",
"search": "Çerca",
+ "search-ignored-headings": " #<!-- lascia questa riga esattamente comm'a l'è --> <pre>\n# Elenco de intestaçioin che saian ignoræ da-a riçerca.\n# E modifiche a questa paggina saian effettive non apen-a a paggina a saiâ indiçizâ.\n# Ti poeu forçâ a re-indiçizzaçion de 'na paggina effettoando una modifica nulla.\n# A scintasci a l'è a seguente:\n# * Tutto da-o carattere \"#\" a-a fin da riga o l'è un commento\n# * Tutte e righe non voeue son e intestaçioin esatte da ignorâ, maiuscolo/minuscolo e tutto\nNotte\nVoxe correlæ\nCollegamenti esterni\n #</pre> <!-- lascia questa riga esattamente comm'a l'è -->",
"searchbutton": "Çerca",
"go": "Vanni",
"searcharticle": "Vanni",
"createacct-yourpasswordagain-ph": "Conferma a password un'atra votta",
"userlogin-remembermypassword": "Mantegnime collegou",
"userlogin-signwithsecure": "Adoeuvia una conescion segua",
+ "cannotlogin-title": "Imposcibbile intrâ",
"cannotloginnow-title": "Aoa no se poeu intrâ",
"cannotloginnow-text": "Quande s'adoeuvia $1 no se poeu intrâ.",
+ "cannotcreateaccount-title": "Imposcibbile creâ di utençe",
"yourdomainname": "Indirisso do scito:",
"password-change-forbidden": "No ti peu cangiâ poula segretta in questa wiki.",
"externaldberror": "Gh'è stæto un aro co-o server de aotenticaçion esterno, oppû no ti g'hæ i aotorizzaçioin pe aggiornâ o to accesso esterno.",
"botpasswords-updated-body": "A password pe-o bot de nomme \"$1\" de l'utente \"$2\" a l'è stæta aggiornâ.",
"botpasswords-deleted-title": "Password bot scassâ",
"botpasswords-deleted-body": "A password pe-o bot de nomme \"$1\" de l'utente \"$2\" a l'è stæta scassâ.",
- "botpasswords-newpassword": "A noeuva password pe accede con <strong>$1</strong> a l'è <strong>$2</strong>. <em>Marchitelo pe rifeimento futuo.</em>",
+ "botpasswords-newpassword": "A noeuva password pe accede con <strong>$1</strong> a l'è <strong>$2</strong>. <em>Marchitelo pe rifeimento futuo.</em><br> (Pe-i vegi bot che g'han de besoeugno che o nomme pe accede o segge o mæximo che o nomme utente, ti poeu doeuviâ <strong>$3</strong> comme nomme utente e <strong>$4</strong> comme password.)",
"botpasswords-no-provider": "BotPasswordsSessionProvider o no l'è disponibbile.",
"botpasswords-restriction-failed": "E restriçioin de password bot impediscian questo accesso.",
"botpasswords-invalid-name": "O nomme utente indicou o no conten o separatô pe-o password bot (\"$1\").",
"invalid-content-data": "Dæti contegnui non vallidi",
"content-not-allowed-here": "Contegnuo in \"$1\" non consentio inta paggina [[$2]]",
"editwarning-warning": "Lasciâ sta paggina porriæ caosâ a perdia de tutte e modiffiche fæte.\nSe ti t'hê introu, ti peu disattivâ st'aviso inta seçion \"{{int:prefs-editing}}\" de teu preferençe.",
+ "editpage-invalidcontentmodel-title": "Modello de contegnuo non supportou",
+ "editpage-invalidcontentmodel-text": "O modello de contegnuo \"$1\" o no l'è supportou.",
"editpage-notsupportedcontentformat-title": "Formato contegnuo non supportou",
"editpage-notsupportedcontentformat-text": "O formato do contegnuo $1 o no l'è supportou da-o modello de contegnuo $2.",
"content-model-wikitext": "wikitesto",
"grant-group-high-volume": "Esegue açioin mascive",
"grant-group-customization": "Personalizzaçion e preferençe",
"grant-group-administration": "Esegue açioin amministrative",
+ "grant-group-private-information": "O l'accede a-i dæti privæ sciu de ti",
"grant-group-other": "Attivitæ varrie",
"grant-blockusers": "Blocca e sblocca utenti",
"grant-createaccount": "Crea un'utença",
"grant-highvolume": "Modiffiche mascive",
"grant-oversight": "Asconde i utenti e soprimme e revixoin",
"grant-patrol": "Marca e modifiche a-e paggine comme veificæ",
+ "grant-privateinfo": "O l'accede a de informaçioin privæ",
"grant-protect": "Proteze e sproteze e paggine",
"grant-rollback": "Rollback de modifiche a-e pagine",
"grant-sendemail": "Manda di email a di atri utenti",
"action-applychangetags": "appricâ di etichette a-e to modiffiche",
"action-changetags": "Azonze e levâ de specifiche etichette sciu scingole verscioin o voxe de registro",
"action-deletechangetags": "scassâ i etichette da-o database",
+ "action-purge": "aggiornâ questa paggina",
"nchanges": "$1 {{PLURAL:$1|modiffica|modiffiche}}",
"enhancedrc-since-last-visit": "$1 {{PLURAL:$1|da l'urtima vixita}}",
"enhancedrc-history": "cronologia",
"file-thumbnail-no": "O nomme do file comença con <strong>$1</strong>; pâ ch'o segge un'inmaggine de dimenscioin redute ''(miniatua)''.\nSe ti dispon-i del'immaggine inta risoluçion originale, carreghila. Sedunque, pe piaxei, cangighe o nomme.",
"fileexists-forbidden": "Un file con questo nomme o l'existe za e o no poeu ese soviascrito. Se ti voeu caregâ o to file, torna inderê e dagghe un atro nomme. [[File:$1|thumb|center|$1]]",
"fileexists-shared-forbidden": "Un file con questo nomme o l'esiste za inte l'archivio de risorse multimediæ condivise. Se ti dexiddei ancon caregâ o file, torna inderê e modifica o nomme co-o quæ caregâ o file. [[File:$1|thumb|center|$1]]",
+ "fileexists-no-change": "O file caregou o l'è un duplicou esatto de l'attoale verscion de <strong>[[:$1]]</strong>.",
+ "fileexists-duplicate-version": "O file caregou o l'è un duplicou esatto de {{PLURAL:$2|'na verscion precedente|verscioin precedente}} de <strong>[[:$1]]</strong>.",
"file-exists-duplicate": "Questo file o l'è un dupricou {{PLURAL:$1|do seguente|di seguenti}} file:",
"file-deleted-duplicate": "Un file identico a questo ([[:$1]]) o l'è stæto scassou into passou. Verifica a cronologia de scassatue primma de caregâlo torna.",
"file-deleted-duplicate-notitle": "Un file identico a questo o l'è stæto scassou into passou, e o tittolo o l'è stæto soppresso. Domanda a quarcun ch'o g'ha a poscibilitæ de vedde i file soppresci de esaminâ a scituaçion primma de procede torna a-o caregamento.",
"filerevert-submit": "Ripristina",
"filerevert-success": "'''O file [[Media:$1|$1]]''' o l'è stæto ripristinou a-a [$4 verscion do $2, $3].",
"filerevert-badversion": "No gh'è de verscioin locali precedenti do file co-o timestamp provisto.",
+ "filerevert-identical": "A verscion attoale do file a l'è za identica a quella seleçionâ.",
"filedelete": "Scassa \"$1\"",
"filedelete-legend": "Scassa o file",
"filedelete-intro": "Ti stæ pe scassâ o file '''[[Media:$1|$1]]''' con tutta a so cronologia.",
"watchnologin": "Accesso non effettuou",
"addwatch": "Azonzi a-a lista sotta öservaçion",
"addedwatchtext": "\"[[:$1]]\" e a so paggina de discuscion son stæte azonte a-a proppia [[Special:Watchlist|lista di öservæ]].",
+ "addedwatchtext-talk": "\"[[:$1]]\" e a so paggina associâ son stæte azonte a-a to [[Special:Watchlist|lista di öservæ]].",
"addedwatchtext-short": "A pagina \"$1\" a l'è stæata azonta a-a proppia lista di öservæ.",
"removewatch": "Rimoeuvi da-i öservæ speciali",
"removedwatchtext": "\"[[:$1]]\" e a so paggina de discuscion son stæte rimosse da-a proppia [[Special:Watchlist|lista di öservæ]].",
+ "removedwatchtext-talk": "\"[[:$1]]\" e a so paggina associâ son stæte rimosse da-a to [[Special:Watchlist|lista di öservæ]].",
"removedwatchtext-short": "A pagina \"$1\" a l'è stæata rimossa da-a proppia lista di öservæ.",
"watch": "Metti sotta oservaçion",
"watchthispage": "Vigilâ 'sta paggina",
"rollbacklinkcount-morethan": "rollback de ciù de {{PLURAL:$1|una modiffica|$1 modiffiche}}",
"rollbackfailed": "Rollback fallio",
"rollback-missingparam": "Parammetri obrigatoi mancanti inta recesta.",
+ "rollback-missingrevision": "Imposcibile caregâ i dæti da verscion.",
"cantrollback": "No se peu tornâ inderê; l'utente ch'o l'ha fæto quelle modiffiche o l'è stæto l'unico contribuente.",
"alreadyrolled": "No l'è poscibbile annullâ e modiffiche apportæ a-a pagina [[:$1]] da parte de [[User:$2|$2]] ([[User talk:$2|discuscion]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]); un atro utente o l'ha zà modificou a pagina oppù o l'ha effettuou o rollback.\n\nA modifica ciù reçente a.a paggina a l'è stæta apportâ da [[User:$3|$3]] ([[User talk:$3|discuscion]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
"editcomment": "L'ogetto da modiffica o l'ea: <em>$1</em>.",
"undeletehistorynoadmin": "Questa pagina a l'è stæta scassâ.\nO motivo da scassatua o l'è mostrou chì sotta, insemme a-i detaggi de l'utente ch'o l'ha modificou questa pagina primma da scassatua.\nO testo contegnuo inte verscioin scassæ o l'è disponibile solo a-i amministratoî.",
"undelete-revision": "Verscion scassâ da pagina $1, inseia o $4 a $5 da $3:",
"undeleterevision-missing": "Verscion errâ o mancante. O collegamento o l'è errou o dunque a verscion a l'è stæta zà ripristinâ ò eliminâ da l'archivvio.",
+ "undeleterevision-duplicate-revid": "No s'è posciuo ripristinâ {{PLURAL:$1|una verscion|$1 verscioin}}, percose {{PLURAL:$1|o so}} <code>rev_id</code> o l'ea za in doeuvia.",
"undelete-nodiff": "No l'è stæto trovou nisciun-a verscion precedente.",
"undeletebtn": "Ristorâ",
"undeletelink": "fanni védde/repìggia",
"pageinfo-article-id": "ID da paggina",
"pageinfo-language": "Lengua do contegnuo da paggina",
"pageinfo-content-model": "Modello do contegnuo da paggina",
+ "pageinfo-content-model-change": "cangia",
"pageinfo-robot-policy": "Indiçizzaçion pe-i robot",
"pageinfo-robot-index": "Consentio",
"pageinfo-robot-noindex": "Non consentio",
"tag-filter": "Filtra pe [[Special:Tags|etichetta]]:",
"tag-filter-submit": "Filtro",
"tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Etichetta|Etichette}}]]: $2)",
+ "tag-mw-contentmodelchange": "cangio a-o modello di contegnui",
"tags-title": "Etichette",
"tags-intro": "Questa pagina a l'elenca i etichette che o software o poriæ associâ a 'na modiffica e o so scignificou.",
"tags-tag": "Nomme de l'etichetta",
"tags-actions-header": "Açioin",
"tags-active-yes": "Sci",
"tags-active-no": "No",
- "tags-source-extension": "Definio da 'n'estenscion",
+ "tags-source-extension": "Definio da-o programma",
"tags-source-manual": "Appricou manoalmente da utenti e bot",
"tags-source-none": "No ciù in doeuvia",
"tags-edit": "cangia",
"createacct-yourpasswordagain-ph": "Įveskite slaptažodį dar kartą",
"userlogin-remembermypassword": "Įsiminti mane",
"userlogin-signwithsecure": "Naudoti saugią jungtį",
+ "cannotlogin-title": "Negalima prisijungti",
+ "cannotlogin-text": "Prisijungti neįmanoma.",
"cannotloginnow-title": "Dabar negalima prisijungti",
"cannotloginnow-text": "Prisijungimas negalimas, kai naudojama $1.",
+ "cannotcreateaccount-title": "Negali kurti paskyrų",
+ "cannotcreateaccount-text": "Tiesioginis paskyros kūrimas nėra įgalintas šioje viki.",
"yourdomainname": "Jūsų domenas:",
"password-change-forbidden": "Jus negalite keisti slaptažodžių šioje wiki.",
"externaldberror": "Yra arba išorinė autorizacijos duomenų bazės klaida arba jums neleidžiama atnaujinti jūsų išorinės paskyros.",
"file-thumbnail-no": "Failo pavadinimas prasideda <strong>$1</strong>.\nAtrodo, kad yra sumažinto dydžio paveikslėlis ''(miniatiūra)''.\nJei jūs turite šį paveisklėlį pilna raiška, įkelkite šitą, priešingu atveju prašome pakeisti failo pavadinimą.",
"fileexists-forbidden": "Failas tokiu pačiu vardu jau egzistuoja ir negali būti perrašytas;\nprašome eiti atgal ir įkelti šį failą kitu vardu. [[File:$1|thumb|center|$1]]",
"fileexists-shared-forbidden": "Failas tokiu vardu jau egzistuoja bendrojoje failų saugykloje;\nJei visvien norite įkelti savo failą, prašome eiti atgal ir įkelti šį failą kitu vardu. [[File:$1|thumb|center|$1]]",
+ "fileexists-no-change": "Įkėlimas yra <strong>[[:$1]]</strong> dabartinės versijos tikslus dublikatas.",
+ "fileexists-duplicate-version": "Įkėlimas yra <strong>[[:$1]]</strong> {{PLURAL:$2|senesnės versijos|senesnių versijų}} tikslus dublikatas.",
"file-exists-duplicate": "Šis failas yra {{PLURAL:$1|šio failo|šių failų}} dublikatas:",
"file-deleted-duplicate": "Failas, identiškas šiam failui ([[:$1]]), seniau buvo ištrintas. Prieš įkeldami jį vėl patikrinkite šio failo ištrynimo istoriją.",
"file-deleted-duplicate-notitle": "Rinkmena, visiškai atitinkanti šią, anksčiau buvo ištrinta, o jos pavadinimas uždraustas. Jums reiktų paprašyti kieno nors, turinčio galimybę peržiūrėti uždraustą rinkmeną, kad jis išaiškintų padėtį, prieš bandant vėl kelti rinkmeną.",
"filerevert-submit": "Grąžinti",
"filerevert-success": "<span class=\"plainlinks\">'''[[Media:$1|$1]]''' buvo sugrąžintas į versiją $4 ($2, $3).</span>",
"filerevert-badversion": "Nėra jokių ankstesnių vietinių šio failo versijų su pateiktu laiku.",
+ "filerevert-identical": "Dabartinė failo versija jau yra identiška pasirinktajai.",
"filedelete": "Trinti $1",
"filedelete-legend": "Trinti rinkmeną",
"filedelete-intro": "Jūs ketinate ištrinti failą '''[[Media:$1|$1]]''' su visa istorija.",
"pageinfo-article-id": "Puslapio ID",
"pageinfo-language": "Puslapio turinio kalba",
"pageinfo-content-model": "Puslapio turinio modelis",
+ "pageinfo-content-model-change": "keisti",
"pageinfo-robot-policy": "Robotų indeksavimas",
"pageinfo-robot-index": "Leidžiama",
"pageinfo-robot-noindex": "Neleidžiama",
"pagecategories": "{{PLURAL:$1|श्रेणी|श्रेणीसभ}}",
"category_header": "श्रेणी \"$1\" मे पन्ना सभ",
"subcategories": "उपश्रेणी",
- "category-media-header": "शà¥\8dरà¥\87णà¥\80 \"$1\" मà¥\87 मà¥\80डिया",
+ "category-media-header": "शà¥\8dरà¥\87णà¥\80 \"$1\" मà¥\87 मिडिया",
"category-empty": "<em>ई श्रेणीमे ई समय कोनो पृष्ठ या मिडिया नै अछि।</em>",
"hidden-categories": "{{PLURAL:$1|नुकाएल श्रेणी|नुकाएल श्रेणीसभ}}",
"hidden-category-category": "नुकाएल श्रेणीसभ",
"broken-file-category": "पन्नासभ जाइमे फाइल लिङ्कसभ टूटल हुअए",
"about": "क विषयमे",
"article": "सामग्री लेख",
- "newwindow": "(नव à¤\96िडà¤\95à¥\80सà¤\81 à¤\96à¥\81à¤\9cà¥\88à¤\9b)",
+ "newwindow": "(नव विनà¥\8dडà¥\8bमà¥\87 à¤\96à¥\81à¤\9cत)",
"cancel": "रद्द करी",
"moredotdotdot": "आर...",
"morenotlisted": "ई पुरा सूची नै छी।",
"otherlanguages": "अन्य भाषासभमे",
"redirectedfrom": "($1सँ पुनर्निर्देशित)",
"redirectpagesub": "पृष्ठ पुनर्निर्देशित करी",
- "redirectto": "क अनुप्रेषित:",
- "lastmodifiedat": "à¤\88 पà¥\83षà¥\8dठà¤\95 पहिनà¥\81à¤\95ा बदलाव $1 à¤\95à¥\87 $2 बà¤\9cà¥\87 à¤à¤\8fल छल।",
+ "redirectto": "कऽ अनुप्रेषित:",
+ "lastmodifiedat": "à¤\88 पà¥\83षà¥\8dठà¤\95 पहिनà¥\81à¤\95ा बदलाव $1 à¤\95à¥\87 $2 बà¤\9cà¥\87 à¤à¥\87ल छल।",
"viewcount": "ई पृष्ठ {{PLURAL:$1|एक|$1}} बेर देखल गेल छल।",
"protectedpage": "सुरक्षित पृष्ठ",
"jumpto": "एतय जाए:",
"yourpasswordagain": "कुटशब्द फेरसँ टाइप करी:",
"createacct-yourpasswordagain": "कुटशब्द जाँच करी",
"createacct-yourpasswordagain-ph": "कुटशब्द पुनः लिखी",
- "remembermypassword": "ई ब्राउजर पर हमर सम्प्रवेश याद राखी (अधिकतम $1 {{PLURAL:$1|दिन|दिनधरि}}क लेल)",
"userlogin-remembermypassword": "हमरा सम्प्रवेशित राखी",
"userlogin-signwithsecure": "सुरक्षित कनेक्शनक प्रयोग करी",
"cannotloginnow-title": "अखन प्रवेश नै भऽ रहल अछि",
"gotaccountlink": "सम्प्रवेश",
"userlogin-resetlink": "अपन सम्प्रवेश विवरण बिसरि गेलौ?",
"userlogin-resetpassword-link": "अपन कूटशब्द बिसरि गेलौ?",
- "userlogin-helplink2": "सम्प्रवेशित करवाकलेल मदत",
+ "userlogin-helplink2": "सम्प्रवेशित करवाक लेल मदति",
"userlogin-loggedin": "अहाँ {{GENDER:$1|$1}}क रूपमे पहिनेसँ सम्प्रवेशित छी।\nकोनो दोसर सदस्यक रुपमे सम्प्रवेशित करवाक लेल देल गेल फारमके प्रयोग करी।",
"userlogin-reauth": "अहाँ {{GENDER:$1|$1}} छी, एहि लेल अहाँक एक बेर आर खातामे प्रवेश करै पडत।",
"userlogin-createanother": "दोसर खाता बनाबी",
"createaccountreason": "कारण:",
"createacct-reason": "कारण:",
"createacct-reason-ph": "अहा इगो आर दोसर खाता कियाक बनउने जा रहल छि",
- "createacct-submit": "à¤\85पन à¤\96ाता बनाà¤\89",
+ "createacct-submit": "à¤\85पन à¤\96ाता बनाबà¥\80",
"createacct-another-submit": "खाता बनाबी",
- "createacct-benefit-heading": "{{SITENAME}} à¤\85हाà¤\81 à¤\9cà¥\8bà¤\95ा लà¥\8bà¤\97सà¤à¤¦à¥\8dवारा बनाà¤\8fल à¤\97à¤\8fल अछि।",
+ "createacct-benefit-heading": "{{SITENAME}} à¤\85हाà¤\81 à¤\9cà¥\8bà¤\95ा लà¥\8bà¤\97सà¤à¤¦à¥\8dवारा बनाà¤\8fल à¤\97à¥\87ल अछि।",
"createacct-benefit-body1": "$1 {{PLURAL:$1|सम्पादन|सम्पादनसभ}}",
"createacct-benefit-body2": "{{PLURAL:$1|पन्ना|पन्नासभ}}",
"createacct-benefit-body3": "सन्निकट {{PLURAL:$1|योगदानकर्ता|योगदानकर्तासभ}}",
"sectioneditnotsupported-text": "ई पृष्ठ पर अनुभाग सम्पादन समर्थित नै अछि",
"permissionserrors": "आज्ञा गल्ती",
"permissionserrorstext": "अहाँके ऐ लेल अनुमति नै अछि, ऐ ले {{PLURAL:$1|कारण|कारणसभ}}:",
- "permissionserrorstext-withaction": "अहाँके अनुमति नै अछि $2 लेल, ऐ लेल {{PLURAL:$1|कारण|कारणसभ}}सँ:",
+ "permissionserrorstext-withaction": "अहाँक अनुमति नै अछि $2 लेल, एकर लेल {{PLURAL:$1|कारण|कारणसभ}}सँ:",
"recreate-moveddeleted-warn": "'''चेतौनी''': अहाँ फेरसँ ओ पन्ना बना रहल छी जे पहिने मेटा देल गेल छै।'''\n\nअहाँ विचारू जे की ई सम्पादन केनाइ उचित अछि।\nऐ पन्नाक मेटाएल बला आ हटाएल वृत्तलेख एतए सुविधा लेल देल जा रहल अछि:",
- "moveddeleted-notice": "ई पन्ना मेटा देल गेल अछि।\nऐ पन्ना लेल मेटाएल आ हटाएल बला वृत्तलेख सन्दर्भ लेल नीचाँ देल गेल अछि।",
+ "moveddeleted-notice": "ई पन्ना मेटाएल गेल अछि।\nई पन्ना लेल मेटाएल आ स्थानान्तरणक लग सन्दर्भ लेल नीचाँ देल गेल अछि।",
"log-fulllog": "सभटा वृत्तलेख देखी",
"edit-hook-aborted": "सम्पादन नोकसीसँ खतम भेल।\nई कोनो कारण नै देलक।",
"edit-gone-missing": "पन्ना अद्यतन नै भऽ सकल।\nलगैए जे ई मेटा देल गेल अछि।",
"revertmerge": "नै मिज्झर",
"mergelogpagetext": "नीचाँ एक पन्ना इतिहासक दोसरमे अद्यतन मिश्रणक सूची अछि।",
"history-title": "\"$1\" क संशोधन इतिहास",
- "difference-title": "\"$1\" à¤\95à¥\87 à¤\85वतरणसà¤à¤®à¥\87 à¤\85à¤\82तर",
+ "difference-title": "\"$1\" à¤\95à¥\87 à¤\85वतरणसà¤à¤®à¥\87 à¤\85नà¥\8dतर",
"difference-title-multipage": "\"$1\" आर \"$2\" पृष्ठसभ मे अंतर",
"difference-multipage": "(पन्ना सभक बीचमे अन्तर)",
"lineno": "पंक्त्ति $1:",
"next-page": "अगला पृष्ठ",
"prevn-title": "पहिलुका $1 {{PLURAL:$1|परिणाम|परिणामसभ}}",
"nextn-title": "आगाँ $1 {{PLURAL:$1|परिणाम|परिणामसभ}}",
- "shown-title": "पà¥\8dरति पनà¥\8dना $1 {{PLURAL:$1|परिणाम|परिणामसà¤}} दà¥\87à¤\96ाà¤\89",
+ "shown-title": "पà¥\8dरति पनà¥\8dना $1 {{PLURAL:$1|परिणाम|परिणामसà¤}} दà¥\87à¤\96ाबà¥\80",
"viewprevnext": "देखी ($1 {{int:pipe-separator}} $2) ($3)",
"searchmenu-exists": "<strong>ऐ विकीपर एकटा पन्ना अछि \"[[:$1]]\" नामसँ।<strong>{{PLURAL:$2|0=|अन्य भेटल परिणामसभ सेहो देखी}}",
"searchmenu-new": "''' पन्ना निर्माण \"[[:$1]]\" ऐ विकीपर !'''",
"enhancedrc-history": "इतिहास",
"recentchanges": "लगक परिवर्तनसभ",
"recentchanges-legend": "नव परिवर्तन सम्बन्धी विकल्प",
- "recentchanges-summary": "ई पन्नापर विकीमे भेल सभसँ अद्यतन परिवर्तनपर नजरि राखू।",
+ "recentchanges-summary": "ई पन्नापर विकीमे भेल सभ सँ अद्यतन परिवर्तनपर नजरि राखी।",
"recentchanges-noresult": "इ अवधिके दौरान इ मापदंडके पूर्ण करेत समय कोनो परिवर्तन नै केएल गेल अछि।",
"recentchanges-feed-description": "ई सूचना-तंत्रांशमे विकीमे भेल सभसँ लगक परिवर्तन ताकी।",
"recentchanges-label-newpage": "ई सम्पादन एकटा नव पन्नाक निर्माण केलक।",
"recentchanges-label-minor": "ई एकटा लघु सम्पादन छी",
"recentchanges-label-bot": "ई सम्पादन यान्त्रिक छल।",
"recentchanges-label-unpatrolled": "ऐ सम्पादनक पुनरीक्षण अखन धरि नै कएल गेल अछि।",
- "recentchanges-label-plusminus": "पन्नाके आकार इ बाइट संख्यासे बदलल गेल",
+ "recentchanges-label-plusminus": "पन्ना आकार ई बाइट सङ्ख्या सँ बदलल गेल",
"recentchanges-legend-heading": "<strong>कुञ्जी:</strong>",
"recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} ([[Special:NewPages|नव पन्नसभक सूची]] सेहो देखी)",
"rcnotefrom": "नीचाँमे '''$2''' सँ भेल परिवर्तन अछि ('''$1''' धरि देखाएल)।",
"rc_categories": "संवर्ग सीमित (\"|\" सँ हटाउ)",
"rc_categories_any": "कोनो",
"rc-change-size": "$1",
- "rc-change-size-new": "बदललाक बाद $1 {{PLURAL:$1|बाइट}}",
+ "rc-change-size-new": "परिवरà¥\8dतनक बाद $1 {{PLURAL:$1|बाइट}}",
"newsectionsummary": "/* $1 */ नव अनुभाग",
"rc-enhanced-expand": "वर्णन देखाउ (जावास्क्रिप्ट चाही)",
"rc-enhanced-hide": "विस्तृत जानकारी नुकाबी",
"listfiles-summary": "ई विशिष्ट पन्ना सभटा उपारोपित संचिका देखबैए।\nप्रयोक्ता द्वारा चुनलापर अन्तिम उपारोपित संचिका देखबैत अछि।",
"listfiles_search_for": "ऐ दृश्य-श्रव्य नामले ताकू:",
"listfiles-userdoesnotexist": "प्रयोक्ता खाता \"$1\" पंजीकृत नै अछि।",
- "imgfile": "सà¤\82चिका",
+ "imgfile": "सà¤\9eà¥\8dचिका",
"listfiles": "संचिका सूची",
"listfiles_thumb": "लघुचित्र",
"listfiles_date": "तिथि",
"filehist-filesize": "संचिका आकार",
"filehist-comment": "समीक्षा",
"imagelinks": "फाइलक उपयोग",
- "linkstoimage": "à¤\90 {{PLURAL:$1|पनà¥\8dनाà¤\95 लाà¤\97ि |$1 पनà¥\8dनाà¤\95 लाà¤\97ि}} à¤\90 फाà¤\87लसà¤\81:",
+ "linkstoimage": "à¤\88 {{PLURAL:$1|पà¥\83षà¥\8dठ|$1 पनà¥\8dनासà¤}}मà¥\87 à¤\88 फाà¤\87लà¤\95 लिà¤\99à¥\8dà¤\95 à¤\85à¤\9bि:",
"linkstoimage-more": "$1 सँ बेसी {{PLURAL:$1|page links|पन्ना सभक लागि}} ऐ संचिकाक।\nई सूची देखबैए {{PLURAL:$1|first page link|first $1 page links}} मात्र ऐ संचिकाक।\nएकटा [[Special:WhatLinksHere/$2|पूर्ण सूची]] उपलब्ध अछि।",
- "nolinkstoimage": "à¤\8fà¤\95à¥\8bà¤\9fा पनà¥\8dना नà¥\88 à¤\85à¤\9bि à¤\9cà¤\95र लाà¤\97ि à¤\90 सà¤\82à¤\9aिà¤\95ासà¤\81 हà¥\81à¤\85ए।",
+ "nolinkstoimage": "à¤\8fà¤\95à¥\8bà¤\9fा पनà¥\8dना नà¥\88 à¤\85à¤\9bि à¤\9cà¥\87 à¤\88 सà¤\9eà¥\8dà¤\9aिà¤\95ा सà¤\81 à¤\9cà¥\81डल हà¥\8bए।",
"morelinkstoimage": "देखू [[Special:WhatLinksHere/$1|आर लागि]] ऐ संचिकाक।",
"linkstoimage-redirect": "$1 (संचिका घुमौआ) $2",
"duplicatesoffile": "ऐ संचिकाक {{PLURAL:$1|file is a duplicate|$1 संचिका सभ द्वितीयक अछि}} अछि ([[Special:FileDuplicateSearch/$2|आर वर्णन]]):",
"shared-repo-from": "$1 सँ",
"shared-repo": "एकटा साझी बखारी",
"shared-repo-name-wikimediacommons": "सामान्य विकीमीडिया",
- "upload-disallowed-here": "à¤\85पनà¥\87 यà¥\80 फ़ाà¤\87लà¤\95à¥\87 à¤\85धिलà¥\87à¤\96ित नà¥\88 à¤\95à¥\88रऽ सà¤\95à¥\88 à¤\9bि।",
+ "upload-disallowed-here": "à¤\85हाà¤\81 à¤\88 फाà¤\87लà¤\95à¥\87 à¤\85धिलà¥\87à¤\96ित नà¥\88 à¤\95रि सà¤\95à¥\88त à¤\9bà¥\80।",
"filerevert": "$1 लग घुरु",
"filerevert-legend": "घुराएल संचिका",
"filerevert-intro": "अहाँ संचिका घुराबैले छी '''[[Media:$1|$1]]''' केँ [$4 संस्करण $3, $2 केँ] लग।",
"removewatch": "साकांक्ष सूचीसँ हटाबी",
"removedwatchtext": "अहाँक [[Special:Watchlist|ध्यानसूची]]सँ \"[[:$1]]\" आ एकर चर्चा पृष्ठ हटाएल गेल अछि।",
"removedwatchtext-short": "इ पृष्ठ \"$1\" अहाँ के साकांक्ष सूची मे राखल गेल अछि।",
- "watch": "धà¥\8dयान राà¤\96à¥\81",
+ "watch": "धà¥\8dयान राà¤\96à¥\80",
"watchthispage": "ऐ पृष्ठपर ध्यान राखू",
"unwatch": "छोडी",
"unwatchthispage": "देखनाइ छोडी",
"deleting-backlinks-warning": "'''चेतौनी:''' जे पृष्ठ अहाँ हटावए लेल जा रहल छी वोकरा में [[Special:WhatLinksHere/{{FULLPAGENAME}}|अन्य पृष्ठ]] जुड़एत अछि अथवा वोकरा ट्रान्सक्ल्युड करएत अछि।",
"rollback": "प्रत्यावर्तित सम्पादन",
"rollbacklink": "प्रत्यावर्तन",
- "rollbacklinkcount": "$1 {{PLURAL:$1|समà¥\8dपादन}} पà¥\82रà¥\8dववत à¤\95रà¥\82",
+ "rollbacklinkcount": "$1 {{PLURAL:$1|समà¥\8dपादन}} पà¥\82रà¥\8dववत à¤\95रà¥\80",
"rollbacklinkcount-morethan": "$1 सँ अधिक {{PLURAL:$1|सम्पादन}} पूर्ववत करू",
"rollbackfailed": "प्रत्यावर्तन असफल",
"cantrollback": "सम्पादन आपस नै भऽ सकै अछि;\nअन्तिम योगदान दैबला ऐ पन्नाक एकमात्र लेखक छी।",
"invert": "उनटा चयन",
"tooltip-invert": "ऐ बक्साकेँ सही करू पन्ना परिवर्तनकेँ नुकेबा लेल चयनित नामस्थानक भीतर (आ संग लागल नामस्थान जँ सही कएल अछि तखन)",
"namespace_association": "सम्बद्ध चेन्हासी",
- "tooltip-namespace_association": "à¤\90 बà¤\95à¥\8dसाà¤\95à¥\87à¤\81 सहà¥\80 à¤\95रà¥\82 जइसँ वार्ता आ विषय नामस्थान समाहित कएल जा सकए चुनल नामस्थानमे",
+ "tooltip-namespace_association": "à¤\88 बà¤\95à¥\8dसाà¤\95à¥\87à¤\81 सहà¥\80 à¤\95रà¥\80 जइसँ वार्ता आ विषय नामस्थान समाहित कएल जा सकए चुनल नामस्थानमे",
"blanknamespace": "(मुख्य)",
"contributions": "{{GENDER:$1|प्रयोगकर्ता}} योगदान",
"contributions-title": "$1 लेल प्रयोक्ताक अवदान",
"nocontribs": "कोनो परिवर्तन ऐ सँ मेल नै खाइए।",
"uctop": "(शिखर)",
"month": "माससँ (आ पहिने)",
- "year": "à¤\90 साल (आ पहिने)",
+ "year": "à¤\88 साल (आ पहिने)",
"sp-contributions-newbies": "मात्र नव खाताक योगदान देखाबी",
"sp-contributions-newbies-sub": "नब प्रयोक्ताकऽ लेल",
"sp-contributions-newbies-title": "नब प्रयोक्ताकऽ योगदान",
"sp-contributions-newonly": "मात्र ओइ सम्पादन देखाउ जे पृष्ठ निर्मित भेल अछि",
"sp-contributions-submit": "ताकू",
"whatlinkshere": "एतय कोन लिङ्क अछि",
- "whatlinkshere-title": "\"$1\" सँ सम्बन्धित पन्ना सभ",
+ "whatlinkshere-title": "\"$1\" सँ सम्बन्धित पन्नासभ",
"whatlinkshere-page": "पन्ना:",
"linkshere": "ई सभ पन्ना सम्बन्धित अछि '''[[:$1]]''':",
"nolinkshere": "'''[[:$1]]''' पर कोनो पन्नाक लागि नै अछि।",
"nolinkshere-ns": "कोनो पन्नाक लागि '''[[:$1]]''' चुनल नामगाममे नै अछि।",
- "isredirect": "पनà¥\8dनाà¤\95à¥\87à¤\81 à¤\98à¥\81राà¤\89",
+ "isredirect": "पà¥\81नरà¥\8dनिरà¥\8dदà¥\87शन पà¥\83षà¥\8dठ",
"istemplate": "परागत",
- "isimage": "फाइलकऽ जडी",
+ "isimage": "फाइल लिङ्क",
"whatlinkshere-prev": "{{PLURAL:$1|पहिलुका|पहिलुका $1}}",
"whatlinkshere-next": "{{PLURAL:$1|अगुलका|अगुलका $1}}",
"whatlinkshere-links": "← जडीसभ",
- "whatlinkshere-hideredirs": "$1 बदलà¥\87न नà¥\81à¤\95ाबà¥\80",
- "whatlinkshere-hidetrans": "$1 पराà¤\97त",
- "whatlinkshere-hidelinks": "$1 समà¥\8dबनà¥\8dध सà¤",
+ "whatlinkshere-hideredirs": "$1 पà¥\81नरà¥\8dनिरà¥\8dदà¥\87श",
+ "whatlinkshere-hidetrans": "$1 à¤\9fà¥\8dरानà¥\8dसà¥\8dà¤\95à¥\8dलà¥\8dयà¥\81à¤\9cनà¥\8dस",
+ "whatlinkshere-hidelinks": "$1 लिà¤\99à¥\8dà¤\95",
"whatlinkshere-hideimages": "$1 फाइल जडी सभ",
- "whatlinkshere-filters": "चलनी सभ",
+ "whatlinkshere-filters": "चलनीसभ",
"autoblockid": "स्वतःप्रतिबन्धित #$1",
"block": "प्रयोक्ताकेँ प्रतिबन्धित करू",
"unblock": "प्रयोक्ताकेँ प्रतिबन्धसँ हटाउ",
"movepage-page-moved": "पन्ना $1 केँ $2 लग घसका देल गेल अछि।",
"movepage-page-unmoved": "पन्ना $1 केँ $2 लग नै घसकाएल जा सकैए।",
"movepage-max-pages": "बेसी सें बेसी $1 पृष्ठ बदलि के {{PLURAL:$1| क देल गेल अछि|क देल गेल अछि}}, आब आर पृष्ठ अपने आप नहि बदलत.",
- "movelogpage": "वà¥\83तà¥\8dतलà¥\87à¤\96 हà¤\9fाà¤\89",
+ "movelogpage": "सà¥\8dथानानà¥\8dतरण लà¤\97",
"movelogpagetext": "नाम बदलल गेल लेख कऽ सूचि नीचां देल गेल अछि",
"movesubpage": "{{PLURAL:$1|उप पन्ना|उप पन्ना}}",
"movesubpagetext": "नीचां $1 {{PLURAL:$1| पन्ना देखाओल गएल अछि, जे अहि पन्नाकऽ उप पन्ना अछि|पन्ना देखावोल गएल अछि, जे अहि पन्नाकऽ उप पन्ना अछि}}।",
"tooltip-pt-mytalk": "{{GENDER:|अहाँक}} वार्ता पृष्ठ",
"tooltip-pt-anontalk": "ऐ अनिकेतसँ भेल सम्पादनक वार्ता",
"tooltip-pt-preferences": "{{GENDER:|अहाँक}} अभिरुचीसभ",
- "tooltip-pt-watchlist": "पन्ना सभ जकर परिवर्त्तन पर अहाँक नजरि अछि",
+ "tooltip-pt-watchlist": "पन्नासभ जेकर परिवर्तन पर अहाँक नजरि अछि",
"tooltip-pt-mycontris": "{{GENDER:|अहाँक}} योगदानक सूची",
- "tooltip-pt-login": "à¤\85हाà¤\81à¤\95 à¤\96ाता à¤\96à¥\8bलà¤\95 लà¥\87ल पà¥\8dरà¥\8bतà¥\8dसाहित à¤\95à¥\87ल à¤\9cाà¤\8fत अछि; मुदा ई अनिवार्य नै अछि",
+ "tooltip-pt-login": "à¤\85हाà¤\81à¤\95 à¤\96ाता à¤\96à¥\8bलà¤\95 लà¥\87ल पà¥\8dरà¥\8bतà¥\8dसाहित à¤\95à¤\8fल à¤\9cाà¤\87त अछि; मुदा ई अनिवार्य नै अछि",
"tooltip-pt-logout": "फेर आयब",
- "tooltip-pt-createaccount": "à¤\85हाà¤\81à¤\95 à¤\96ाता à¤\96à¥\8bलà¤\95 लà¥\87ल पà¥\8dरà¥\8bतà¥\8dसाहित à¤\95à¥\87ल à¤\9cाà¤\8fत à¤\85à¤\9bि; मà¥\81दा à¤\88 à¤\85निवारà¥\8dय नà¥\88 à¤\9bà¥\88",
+ "tooltip-pt-createaccount": "à¤\85हाà¤\81à¤\95 à¤\96ाता à¤\96à¥\8bलà¤\95 लà¥\87ल पà¥\8dरà¥\8bतà¥\8dसाहित à¤\95à¤\8fल à¤\9cाà¤\87त à¤\85à¤\9bि; मà¥\81दा à¤\88 à¤\85निवारà¥\8dय नà¥\88 à¤\85à¤\9bि",
"tooltip-ca-talk": "विषयसूचीक पन्नाक सम्बन्धमे वर्त्तालाप",
"tooltip-ca-edit": "ई पन्नाक सम्पादित करी",
"tooltip-ca-addsection": "नव खण्ड शुरू करी",
- "tooltip-ca-viewsource": "à¤\90 पनà¥\8dनापर वरदहसà¥\8dत à¤\9bà¥\88।\nà¤\85हाà¤\81 à¤\8fà¤\95र à¤\9cड़ि दà¥\87à¤\96 सà¤\95à¥\88 à¤\9bà¥\80।",
+ "tooltip-ca-viewsource": "à¤\88 पनà¥\8dना सà¤\82रà¤\95à¥\8dषित à¤\85à¤\9bि ।\nà¤\85हाà¤\81 à¤\8fà¤\95र सà¥\8dरà¥\8bत दà¥\87à¤\96 सà¤\95à¥\88 à¤\9bà¥\80 ।",
"tooltip-ca-history": "ई पृष्ठक पुरान अवतरण",
"tooltip-ca-protect": "ऐ पन्नाकेँ बचाउ",
"tooltip-ca-unprotect": "ऐ पन्नाक रक्षा कवच बदलू",
"tooltip-ca-delete": "ऐ पन्नाकेँ मेटाउ",
"tooltip-ca-undelete": "ई पन्ना मेटेबासँ पहिने भेल सम्पादन वापस करू",
- "tooltip-ca-move": "à¤\90 पà¥\83षà¥\8dठà¤\95à¥\87à¤\81 हà¤\9fाà¤\89",
+ "tooltip-ca-move": "à¤\88 पà¥\83षà¥\8dठसà¥\8dथानानतरित à¤\95रà¥\80",
"tooltip-ca-watch": "ई पन्नाकेँ अपन साकांक्षसूचीमे राखी",
"tooltip-ca-unwatch": "ऐ पन्नाकेँ हमर साकांक्ष सूचीसँ हटाउ",
"tooltip-search": "{{SITENAME}}मे ताकी",
"tooltip-n-currentevents": "लगक घटनाक विषयमे आधार सूचना प्राप्त करी।",
"tooltip-n-recentchanges": "विकिमे लगक परिवर्तनक सूची",
"tooltip-n-randompage": "कोनो अनिर्धारित पन्ना लोड करी",
- "tooltip-n-help": "पता लà¤\97ावà¤\8f वाला स्थान",
+ "tooltip-n-help": "पता लà¤\97ावà¥\88वाला स्थान",
"tooltip-t-whatlinkshere": "सभ विकी-पन्नाक सूची जकर एतय लिङ्क अछि",
"tooltip-t-recentchangeslinked": "ई पृष्ठक लगक पन्नामे भेल नव परिवर्तनसभ",
"tooltip-feed-rss": "ऐ पन्ना लेल आर.एस.एस. सूचना",
"tooltip-t-print": "ई पृष्ठक छपैबला रूप",
"tooltip-t-permalink": "पृष्ठक ई संस्करणक स्थायी लिङ्क",
"tooltip-ca-nstab-main": "सामग्री वाला पृष्ठ देखी",
- "tooltip-ca-nstab-user": "प्रयोक्ता पन्नाकेँ देखू",
+ "tooltip-ca-nstab-user": "प्रयोक्ता पन्ना देखी",
"tooltip-ca-nstab-media": "मीडिया पृष्ठ देखू",
"tooltip-ca-nstab-special": "ई एकटा विशिष्ट पन्ना छी, आ अहाँ एकरा सम्पादित नै कऽ सकै छी",
- "tooltip-ca-nstab-project": "परियà¥\8bà¤\9cना पनà¥\8dना दà¥\87à¤\96à¥\82",
+ "tooltip-ca-nstab-project": "परियà¥\8bà¤\9cना पनà¥\8dना दà¥\87à¤\96à¥\80",
"tooltip-ca-nstab-image": "सञ्चिकाक पृष्ठ देखी",
"tooltip-ca-nstab-mediawiki": "प्रणालीक सन्देश देखी",
- "tooltip-ca-nstab-template": "नमà¥\82ना दà¥\87à¤\96à¥\82",
+ "tooltip-ca-nstab-template": "नमà¥\82ना दà¥\87à¤\96à¥\80",
"tooltip-ca-nstab-help": "सहायता पृष्ठ देखू",
- "tooltip-ca-nstab-category": "सà¤\82वरà¥\8dà¤\97 पनà¥\8dना दà¥\87à¤\96à¥\82",
+ "tooltip-ca-nstab-category": "शà¥\8dरà¥\87णà¥\80 पनà¥\8dना दà¥\87à¤\96à¥\80",
"tooltip-minoredit": "एकरा मामली सम्पादन चिन्हित करू",
- "tooltip-save": "à¤\85पन परिवरà¥\8dतà¥\8dतनà¤\95à¥\87 सà¥\81रà¤\95à¥\8dषित à¤\95रà¥\82",
- "tooltip-preview": "परिवरà¥\8dतà¥\8dतनà¤\95 पà¥\8dरदरà¥\8dशन, सà¤\82à¤\9cà¥\8bà¤\97बाà¤\95 पहिनà¥\87 à¤\8fà¤\95र पà¥\8dरयà¥\8bà¤\97 à¤\95रà¥\82!",
- "tooltip-diff": "दà¥\87à¤\96ाà¤\8a à¤\9cà¥\87 परिवरà¥\8dतà¥\8dतन à¤\85हाà¤\81 à¤\8fहि लà¥\87à¤\96मà¥\87 à¤\95à¤\8fलहà¥\81à¤\81।",
+ "tooltip-save": "à¤\85पन परिवरà¥\8dतन सà¥\81रà¤\95à¥\8dषित à¤\95रà¥\80",
+ "tooltip-preview": "परिवरà¥\8dतनà¤\95 पà¥\8dरदरà¥\8dशन, सà¤\82रà¤\95à¥\8dषण सà¤\81 पहिनà¥\87 à¤\8fà¤\95र पà¥\8dरयà¥\8bà¤\97 à¤\95रà¥\80!",
+ "tooltip-diff": "à¤\88 पाठमà¥\87 à¤\85हाà¤\81दà¥\8dवारा à¤\95à¤\8fल परिवरà¥\8dतन दà¥\87à¤\96à¥\80।",
"tooltip-compareselectedversions": "ऐ पन्नाक दू टा चयन कएल संशोधनक बीचक अन्तर देखू",
"tooltip-watch": "ऐ पन्नाकेँ अपन साकांक्ष सूचीमे जोड़ू",
"tooltip-watchlistedit-normal-submit": "शीर्षक सभकेँ हटाउ",
"file-info-size": "$1 × $2 चित्राणु, फाइल आकार: $3, माइम प्रकार: $4",
"file-info-size-pages": "$1 × $2 चित्रकण, संचिका आकार : $3, माइम प्रकार: $4, $5 {{PLURAL:$5|पन्ना|पन्ना सभ}}",
"file-nohires": "ऐसँ बेशी आनन्तर्य उपलब्ध नै अछि।",
- "svg-long-desc": "एस.वी.जी. फाइल, मामूली रूपमे $1 × $2 चित्रकण, फाइलक आकार: $3",
+ "svg-long-desc": "एसभिजी फाइल, मामूली रूपमे $1 × $2 चित्रकण, फाइलक आकार: $3",
"svg-long-desc-animated": "एनिमेटेड एस.वी.जी. फाइल,$1 × $2 चित्रकण, फाइलक आकार: $3",
"svg-long-error": "अमान्य एस॰वी॰जी फ़ाइल: $1",
"show-big-image": "पूर्ण आनन्तर्य",
"exif-referenceblackwhite": "कारी आ उज्जर सन्दर्भ मूल्यक जोड़ा",
"exif-datetime": "संचिका परिवर्तन तिथि आ समए",
"exif-imagedescription": "चित्र शीर्षक",
- "exif-make": "à¤\95à¥\88मरा निर्माता",
+ "exif-make": "à¤\95à¥\8dयामरा निर्माता",
"exif-model": "क्यामरा मोडल",
- "exif-software": "पà¥\8dरयà¥\81à¤\95à¥\8dत तà¤\82तà¥\8dराà¤\82श",
+ "exif-software": "पà¥\8dरयà¥\8bà¤\97 à¤\95à¤\8fल सफà¥\8dà¤\9fवà¥\87यर",
"exif-artist": "लिखैबला",
"exif-copyright": "सर्वाधिकारी",
"exif-exifversion": "एक्जिफ संस्करण",
"exif-flashpixversion": "फ्लैशपिक्स संस्करण समर्थित",
- "exif-colorspace": "रà¤\82गक स्थान",
+ "exif-colorspace": "रà¤\99à¥\8dगक स्थान",
"exif-componentsconfiguration": "सभ घटकक अर्थ",
"exif-compressedbitsperpixel": "चित्र संकुचन अवस्था",
"exif-pixelxdimension": "तस्वीरक चौडाई",
"exif-pixelydimension": "तस्वीरक ऊँचाई",
"exif-usercomment": "सदस्यक टिप्पणी",
"exif-relatedsoundfile": "संबंधित ध्वनि फ़ाईल",
- "exif-datetimeoriginal": "डाà¤\9fा बनाबà¥\88à¤\95 तारà¥\80ख आ समय",
- "exif-datetimedigitized": "à¤\85à¤\82à¤\95à¥\80à¤\95रण à¤\95à¥\87 तारà¥\80ख आ समय",
+ "exif-datetimeoriginal": "डाà¤\9fा बनाबà¥\88à¤\95 तारिख आ समय",
+ "exif-datetimedigitized": "à¤\85à¤\99à¥\8dà¤\95à¥\80à¤\95रणà¤\95 तारिख आ समय",
"exif-subsectime": "दिनांकसमयक उपसेकंड",
"exif-subsectimeoriginal": "मूलदिनांकसमयक उपसेकंड",
"exif-subsectimedigitized": "मूलदिनांकअंकीकरणक उपसेकंड",
"logentry-import-upload": "$1 {{GENDER:$2|आयात केल गेल}} $3 संचिका उपारोपन के माध्यम सँ",
"logentry-import-interwiki": "$1 {{GENDER:$2|आयात केल गेल}} $3 कोनो और विकि सँ",
"logentry-merge-merge": "$1 {{GENDER:$2|विलय केल गेल}} $3 के $4 में (संशोधन $5 धरि)",
- "logentry-move-move": "$1 हटाएल पन्ना $3 सँ $4",
+ "logentry-move-move": "$1द्वारा $3 पृष्ठ $4 पर {{GENDER:$2|स्थानान्तरित}} कएलक",
"logentry-move-move-noredirect": "$1 {{GENDER:$2|हटाएल}} पन्ना $3 सँ $4 घुमौआकेँ बिना छोड़ने",
"logentry-move-move_redir": "$1 {{GENDER:$2|हटाएल}} पन्ना $3 सँ $4 घुमौआक अतिरिक्त",
"logentry-move-move_redir-noredirect": "$1 {{GENDER:$2|हटाएल}} पन्ना $3 सँ $4 घुमौआक अतितिक्त घुमौआकेँ बिना छोड़ने",
"createacct-yourpasswordagain-ph": "Повторно внесете ја лозинката",
"userlogin-remembermypassword": "Запомни ме",
"userlogin-signwithsecure": "Користи безбеден опслужувач",
+ "cannotlogin-title": "Не можам да ве најавам",
+ "cannotlogin-text": "Најавата не е возможна.",
"cannotloginnow-title": "Во моментов не можам да ве најавм",
"cannotloginnow-text": "Не можам да ве најавам кога се користи $1.",
+ "cannotcreateaccount-title": "Не можам да создавам сметки",
+ "cannotcreateaccount-text": "Непосредното создавање на сметки не е овозможено на ова вики.",
"yourdomainname": "Вашиот домен:",
"password-change-forbidden": "Не можете да ја менувате лозинката на ова вики.",
"externaldberror": "Настана грешка при надворешното најавување на базата или пак немате дозвола да ја подновите вашата надворешна сметка.",
"botpasswords-updated-body": "Лозинката на ботот со име „$1“ на корисникот „$2“ е изменета.",
"botpasswords-deleted-title": "Лозинка на ботот е избришана",
"botpasswords-deleted-body": "Лозинката на ботот со име „$1“ на корисникот „$2“ е избришана.",
- "botpasswords-newpassword": "Новата лозинка за најава <strong>$1</strong> е <strong>$2</strong>. <em>Запишете си ја за во иднина.</em>",
+ "botpasswords-newpassword": "Новата лозинка за најава <strong>$1</strong> е <strong>$2</strong>. <em>Запишете си ја за во иднина.</em> <br> (За стари ботови што бараат најавното име да биде исто како подоцнежното корисничко име, можете да ги употребите и <strong>$3</strong> како корисничко име и <strong>$4</strong> како лозинка.)",
"botpasswords-no-provider": "BotPasswordsSessionProvider е недостапен.",
"botpasswords-restriction-failed": "Не можете да се најавите поради ограничувањата за лозинки на ботови.",
"botpasswords-invalid-name": "Укажаното корисничко име не го содржи одделувачот ботовска лозинка („$1“).",
"invalid-content-data": "Неважечки податоци од содржината",
"content-not-allowed-here": "Содржините од моделот „$1“ не се допуштени на страницата [[$2]]",
"editwarning-warning": "Ако ја напуштите страницата ќе ги изгубите сите промени кои сте ги направиле.\nАко сте најавени, можете да го исклучите ова предупредување во одделот „{{int:prefs-editing}}“ во вашите нагодувања.",
+ "editpage-invalidcontentmodel-title": "Содржинскиот модел не е поддржан",
+ "editpage-invalidcontentmodel-text": "Содржинскиот модел „$1“ не е поддржан.",
"editpage-notsupportedcontentformat-title": "Форматот на содржината не е поддржан",
"editpage-notsupportedcontentformat-text": "Форматот $1 is не е поддржан од содржинскиот модел $2.",
"content-model-wikitext": "викитекст",
"pageinfo-article-id": "Назнака на страницата",
"pageinfo-language": "Јазик на содржината на страницата",
"pageinfo-content-model": "Модел на содржината на страницата",
+ "pageinfo-content-model-change": "смени",
"pageinfo-robot-policy": "Индексирање со роботи",
"pageinfo-robot-index": "Дозволено",
"pageinfo-robot-noindex": "Недозволено",
"tag-filter": "[[Special:Tags|Филтер за ознаки]]:",
"tag-filter-submit": "Филтер",
"tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Ознака|Ознаки}}]]: $2)",
+ "tag-mw-contentmodelchange": "измена на содржинскиот модел",
+ "tag-mw-contentmodelchange-description": "Уредувања што го [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel менуваат содржинскиот модел] на една страница",
"tags-title": "Ознаки",
"tags-intro": "На оваа страница е даден список на ознаки со кои програмската опрема може да ги означи измените и нивното значење.",
"tags-tag": "Име на ознака",
"tags-actions-header": "Дејства",
"tags-active-yes": "Да",
"tags-active-no": "Не",
- "tags-source-extension": "Ð\9eдÑ\80едени од додаÑ\82ок",
+ "tags-source-extension": "Ð\9eдÑ\80едени од пÑ\80огÑ\80амоÑ\82",
"tags-source-manual": "Применети рачно од корисници и ботови",
"tags-source-none": "Вон употреба",
"tags-edit": "уреди",
"yourpasswordagain": "Têng phah bi̍t-bé:",
"createacct-yourpasswordagain": "Khak-jīn bi̍t-bé",
"createacct-yourpasswordagain-ph": "Koh phah chi̍t-pái bi̍t-bé",
- "remembermypassword": "Tī chit ê liû-lám-khì kì góa ê teng-ji̍p chu-liāu.(siōng chē kì $1 {{PLURAL:$1|kang|kang}})",
"userlogin-remembermypassword": "Kì-lo̍k goá teng-ji̍p--ê chu-liāu",
"userlogin-signwithsecure": "用安全連線",
"yourdomainname": "你的網域:",
"backend-fail-delete": "Bô-hoat-tō· kā tóng-àn \"$1\" thâi tiāu",
"license": "Siū-khoân:",
"license-header": "Siū-khoân",
+ "imgfile": "tóng-àn",
"listfiles": "Iáⁿ-siōng lia̍t-toaⁿ",
"listfiles_date": "Ji̍t-kî",
"listfiles_name": "Miâ",
"yourpasswordagain": "Ripete 'a password:",
"createacct-yourpasswordagain": "Cunferma password",
"createacct-yourpasswordagain-ph": "'Nserisce 'e nuovo 'a password",
- "remembermypassword": "Allicuordate d\"a password (for a maximum of $1 {{PLURAL:$1|day|days}})",
"userlogin-remembermypassword": "Mantienime cullegato",
"userlogin-signwithsecure": "Usa na conessione sicura",
+ "cannotlogin-title": "Nun se pò trasì",
+ "cannotlogin-text": "Trasì nun è possibbele mò.",
"cannotloginnow-title": "Nun se pò trasì mò",
"cannotloginnow-text": "'A connessione nun è possibbele quanno s'ausa $1.",
+ "cannotcreateaccount-title": "Nun se ponno crià cunte",
+ "cannotcreateaccount-text": "'A criazione diretta 'e cunte è stutata int'a stu wiki.",
"yourdomainname": "Spiecà 'o dumminio",
"password-change-forbidden": "Nun se ponno cagnà 'e password ncopp'a sta wiki.",
"externaldberror": "Ce sta n'errore ch' 'e server d'autenticazione esterno, o pure nun v'è permesso accedere all'aghiurnamento d' 'o cunto sterno vuosto.",
"content-json-empty-object": "Oggetto abbacante",
"content-json-empty-array": "Array abbacante",
"deprecated-self-close-category": "Paggene ausanno nu tag HTML auto-nchiuse nun valido",
+ "deprecated-self-close-category-desc": "'A paggena cuntenesse tag HTML auto-nchiuse nun valide, comme <code><b/></code> o <code><span/></code>. 'O cumpurtamento 'e chiste cagnarrà priesto pe' se ffà cuerente a le specifiche HTML5, è pecchesto ca mò nun è cunzigliato (deprecato) ll'uso 'e chiste dint' 'o wikitesto.",
"duplicate-args-warning": "<strong>Attenziò:</strong> [[:$1]] sta chiammanno [[:$2]] cu cchiù 'e nu volore p' 'o parametro \"$3\". Surtanto ll'urdemo valore s'auserrà.",
"duplicate-args-category": "Paggene c'ausano argomiente dupprecate dint' 'e chiammate a 'e mudelle",
"duplicate-args-category-desc": "'A paggena tene chiammate a mudelle c'ausassero argomiente dupprecate, comme p'esempio <code><nowiki>{{foo|bar=1|bar=2}}</nowiki></code> o <code><nowiki>{{foo|bar|1=baz}}</nowiki></code>.",
"grant-group-high-volume": "Secuta attività 'e volume massivo",
"grant-group-customization": "Personalizzaziona e preferenze",
"grant-group-administration": "Secuta aziune ammenistrative",
+ "grant-group-private-information": "Tràse dint' 'e date private ncopp'a tte",
"grant-group-other": "Attività differénte",
"grant-blockusers": "Blocca e sblocca utente",
"grant-createaccount": "Crìa cunte",
"grant-highvolume": "Cagnamiente massive",
"grant-oversight": "Annascunne utente e scancèlla 'e verziune",
"grant-patrol": "Nzègna 'e cagnamiente a 'e paggene comme verificate",
+ "grant-privateinfo": "Tràse 'a 'e nfurmaziune private",
"grant-protect": "Prutegge e sprutegge paggene",
"grant-rollback": "Torna arrèto 'e cagnamiente a 'e paggene",
"grant-sendemail": "Manna na mail a ll'at'utente",
"file-thumbnail-no": "Stu filename accummencia pe' <strong>$1</strong>.\nPare ca ce sta n'immaggene piccerilla <em>(thumbnail)</em>.\nSi tiene st'immaggene 'n risoluzione origginale, pe' piacere carrecatela. Si nò, vedite 'e cagnà 'o nomme d' 'o file.",
"fileexists-forbidden": "Nu file cu stu nomme esiste già, e nun se può sovrascrivere.<br/>\nPe' piacere turnat'arreto e cagnàte 'o nomme p' 'o turnà a carrecà.\n[[File:$1|thumb|center|$1]]",
"fileexists-shared-forbidden": "Nu file cu stu nomme esiste già dint'a l'archivio 'e risorse multimediale spartute. Si vulite carrecà 'o file ancora, turnat'arreto e cagnate 'o nomme p' 'o turnà a carrecà.\n[[File:$1|thumb|center|$1]]",
+ "fileexists-no-change": "'O file carrecato è nu dupprecato eguale eguale d' 'a verziona 'e mò 'e <strong>[[:$1]]</strong>.",
+ "fileexists-duplicate-version": "'O file carrecato è nu duprecato eguale eguale 'a {{PLURAL:$2|na verziona 'e primma|na quantità 'e verziune 'e primma}} 'e <strong>[[:$1]]</strong>.",
"file-exists-duplicate": "Stu file è nu duplicato {{PLURAL:$1|d' 'o|d' 'e}} file ccà abbascio:",
"file-deleted-duplicate": "Nu file identico a chesto ([[:$1]]) è stato scancellato prima. Cuntrullate 'a cronologgia d' 'e scancellamiente apprimma d' 'o carrecà n'ata vota.",
"file-deleted-duplicate-notitle": "Nu file eguale a stu file è stato previamente scancellato, e 'o titolo è stato sbaccantato. Chierete a coccheruno ca tenesse 'a posibbelità 'e vedé file luvate e sbaccantate pe' sapé nquale situazione ve truvate apprimma d' 'o ffà carrecà n'ata vota.",
"filerevert-submit": "Arrepiglia",
"filerevert-success": "'''[[Media:$1|$1]]''' è stat'arripigliato â verziona [$4 d' 'e $3 d' 'o $2].",
"filerevert-badversion": "Nun ce sta na virziona lucale 'e stu file cu l'orario addimannato.",
+ "filerevert-identical": "'A verziona 'e mò d' 'o file è già eguale eguale a chilla scigliuta.",
"filedelete": "Scancella $1",
"filedelete-legend": "Scancella 'o file",
"filedelete-intro": "State pe' scancellà 'o file '''[[Media:$1|$1]]''' cu tutta 'a cronologgia 'e chisto.",
"rollbacklinkcount-morethan": "annulla cchiù 'e {{PLURAL:$1|nu cagnamiento|$1 cagnamiente}}",
"rollbackfailed": "Annullamento fallito",
"rollback-missingparam": "Parammetre obbligate mancante int' 'a richiesta.",
+ "rollback-missingrevision": "Nun se ponno carrecà 'e date d' 'a verziuna.",
"cantrollback": "Nun se può annullà stu cagnamiento;\nsapite ca l'urdemo autore è stato pure sul'isso a faticà dint'a sta paggena (nun ce sta n'at'autore).",
"alreadyrolled": "Nun se può turna arreto a l'urdemo cagnamiento [[:$1]] 'a [[User:$2|$2]] ([[User talk:$2|Chiacchiera]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);\ncocch'ato ha cagnato o annullato 'a paggena già.\n\nL'urdemo cangamiento d' 'a paggena fuje 'a [[User:$3|$3]] ([[User talk:$3|Chiacchiera]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
"editcomment": "'O riepilego d' 'o cagnamiento era: <em>$1</em>.",
"withoutinterwiki-legend": "Prefiks",
"withoutinterwiki-submit": "Vis",
"fewestrevisions": "Sidene med færrast endringar",
- "nbytes": "$1 {{PLURAL:$1|byte|byte}}",
+ "nbytes": "$1 {{PLURAL:$1|byte}}",
"ncategories": "$1 {{PLURAL:$1|kategori|kategoriar}}",
"ninterwikis": "{{PLURAL:$1|éin interwiki|$1 interwikiar}}",
"nlinks": "{{PLURAL:$1|Éi lenkje|$1 lenkjer}}",
"minoredit": "Aquò es un cambiament menor",
"watchthis": "Seguir aquesta pagina",
"savearticle": "Salvar",
+ "savechanges": "Enregistrar los cambiaments",
"publishpage": "Publicar la pagina",
"publishchanges": "Publicar las modificacions",
"preview": "Previsualizar",
"yourpasswordagain": "ପାସୱାର୍ଡ଼ ଆଉଥରେ:",
"createacct-yourpasswordagain": "ପାସୱାର୍ଡ଼ ନିଶ୍ଚିତ କରିବେ",
"createacct-yourpasswordagain-ph": "ଆଉଥରେ ପାସୱାର୍ଡ଼ ଦିଅନ୍ତୁ",
- "remembermypassword": "ଏହି ବ୍ରାଉଜରରେ (ସବୁଠୁ ଅଧିକ ହେଲେ $1 {{PLURAL:$1|day|ଦିନ}}) ପାଇଁ ମୋ ଲଗଇନ ମନେ ରଖିଥିବେ",
"userlogin-remembermypassword": "ମୋତେ ଲଗ-ଇନ କରି ରଖିଥାନ୍ତୁ",
"userlogin-signwithsecure": "ନିରାପଦ କନେକସନ ବ୍ୟବହାର କରନ୍ତୁ",
"cannotloginnow-title": "ଏବେ ଲଗ ଇନ ହୋଇପାରିବ ନାହିଁ",
"passwordreset-emailtext-user": "$1 ନାମକ ସଭ୍ୟଜଣକ {{SITENAME}}ରେ {{SITENAME}} ($4) ପାଇଁ ଆପଣଙ୍କ ପାସ ୱାର୍ଡ଼ ରିସେଟ କରିବାର ଅନୁରୋଧ କରିଛନ୍ତି । ତଳ {{PLURAL:$3|ଖାତାଟି|ଖାତାମାନ}} ଉକ୍ତ ଇମେଲ ସହିତ ସମ୍ବନ୍ଧିତ:\n\n$2\n\n{{PLURAL:$3|ଏହି ଅସ୍ଥାୟୀ ପାସୱାର୍ଡ଼ଟି|ଏହି ଅସ୍ଥାୟୀ ପାସୱାର୍ଡ଼ମାନ}} {{PLURAL:$5|ଦିନକ|$5 ଦିନ}}ରେ ଅଚଳ ହୋଇଯିବ ।\nଆପଣ ଲଗ ଇନ କରି ନୂଆ ପାସୱାର୍ଡ଼ଟିଏ ବାଛନ୍ତୁ । ଯଦି ଆଉ କେହି ଏହି ଅନୁରୋଧଟି କରିଥାନ୍ତି କିମ୍ବା ଆପଣଙ୍କର ନିଜ ପୁରୁଣା ପାସୱାର୍ଡ଼ଟି ମନେପଡ଼ିଗଲା ତେବେ ଆପଣଙ୍କୁ ଆଉ ପାସୱାର୍ଡ଼ ବଦଳାଇବାର ଆବଶ୍ୟକତା ନାହିଁ । ଆପଣ ଏହି ମେସେଜଟିକୁ ଅଣଦେଖା କରି ନିଜର ପୁରୁଣା ପାସୱାର୍ଡ଼ ବ୍ୟବହାର କରୁଥାନ୍ତୁ ।",
"passwordreset-emailelement": "ଇଉଜର ନାମ: \n$1\n\nଅସ୍ଥାୟୀ ପାସୱାର୍ଡ଼: \n$2",
"passwordreset-emailsentemail": "ଏକ ପାସୱାର୍ଡ଼ ପୁନଃସ୍ଥାପନ ଇମେଲ ପଠାଇଦିଆଯାଇଅଛି ।",
- "passwordreset-emailsent-capture": "ତଳେ ଦେଖାଯାଉଥିବା ଭଳି, ପାସୱାର୍ଡ଼ ପୁନଃସ୍ଥାପନ ଇମେଲଟିଏ ପଠାଇଦିଆଯାଇଛି ।",
- "passwordreset-emailerror-capture": "ପାସୱାର୍ଡ଼ ବଦଳାଇବା ସୂଚନା ସହ ଇମେଲଟିଏ ତିଆରି ହୋଇଛି, ଯାହା ତଳେ ଦେଖିପାରିବେ । କିନ୍ତୁ ଏହାକୁ {{GENDER:$2|ସଭ୍ୟ}}ଙ୍କୁ ପଠାଇବାରେ ବିଫଳ ହେଲୁ, କାରଣ: $1",
"changeemail": "ଇ-ମେଲ ଠିକଣା ବଦଳାଇବେ କିମ୍ବା କାଢିବେ",
"changeemail-header": "ଖାତା ଇ-ମେଲ ଠିକଣା ବଦଳାଇବେ",
"changeemail-no-info": "ଏହି ପୃଷ୍ଠାଟିକୁ ସିଧା ଖୋଲିବା ନିମନ୍ତେ ଆପଣଙ୍କୁ ଲଗ ଇନ କରିବାକୁ ପଡ଼ିବ ।",
"undo-nochange": "ଏହି ସମ୍ପାଦନା ପଛକୁ ଫେରାଇଦିଆଯାଇଥିବା ଭଳି ଲାଗୁଛି ।",
"undo-summary": "[[Special:Contributions/$2|$2]] ([[User talk:$2|ଆଲୋଚନା]]) ଙ୍କ ଦେଇ କରାଯାଇଥିବା $1 ସଙ୍କଳନଟି ପଛକୁ ଫେରାଇନିଆଗଲା",
"undo-summary-username-hidden": "ଜଣେ ଅଜଣା ସଭ୍ୟଙ୍କ ଦେଇ ହୋଇଥିବା $1 ସଂସ୍କରଣଟି ପଛକୁ ଫେରାନ୍ତୁ",
- "cantcreateaccounttitle": "ଖାତାଟିଏ ତିଆରି କରାଯାଇପାରିବ ନାହିଁ",
"cantcreateaccount-text": "[[User:$3|$3]]ଙ୍କ ଦେଇ ('''$1''') IP ଠିକଣାରୁ ଖାତା ଖୋଲିବାକୁ ବାରଣ କରାଯାଇଅଛି ।\n\n$3ଙ୍କ ଦେଇ ଦିଆଯାଇଥିବା କାରଣ ହେଲା ''$2''",
"cantcreateaccount-range-text": "ଆପଣଙ୍କ IP Address (<strong>$4</strong>) ସମେତ <strong>$1</strong> ସୀମା ଭିତରେ ଥିବା IP Address ରୁ [[User:$3|$3]]ଙ୍କ ଦ୍ୱାରା ନୂଆ ଖାତା ତିଆରିକୁ ଅଟକାଯାଇଛି ।\n\n$3ଙ୍କ ଦ୍ୱାରା ଏହାର କାରଣ ଦିଆଯାଇଛି: <em>$2</em>",
"viewpagelogs": "ଏହି ପୃଷ୍ଠା ପାଇଁ ଲଗଗୁଡ଼ିକୁ ଦେଖନ୍ତୁ ।",
"thumbnail_image-missing": "ଫାଇଲଟି ନଥିଲା ଭଳି ଲାଗୁଛି : $1",
"thumbnail_image-failure-limit": "ଏହି ଥମ୍ବନେଲ ରେଣ୍ଡର କରିବା ପାଇଁ ନିକଟରେ ଅନେକ ($1 କିମ୍ବା ଅଧିକ) ବିଫଳ ଚେଷ୍ଟା କରାଯାଇଛି । ଆଉଥରେ ଚେଷ୍ଟା କରନ୍ତୁ ।",
"import": "ପୃଷ୍ଠା ଆମଦାନି କରିବେ",
- "importinterwiki": "à¬\9fà\8dରାନà\8dସà¬\89à¬\87à¬\95ି à¬\88ମà\8dପà\8bରà\8dà¬\9f",
+ "importinterwiki": "à¬\86à¬\89 à¬\8fà¬\95 à¬\89à¬\87à¬\95ିରà\81 à¬\86ମଦାନà\80 à¬\95ରନà\8dତà\81",
"import-interwiki-text": "ଏକ ଉଇକି ଓ ପୃଷ୍ଠା ନାମ ଆମଦାନି କରିବା ନିମନ୍ତେ ଦିଅନ୍ତୁ ।\nସଂସ୍କରଣ ତାରିଖ ଓ ସମ୍ପାଦକଙ୍କ ନାମ ସାଇତା ହୋଇ ରହିବ ।\nଅନ୍ତଉଇକି ଆମଦାନି କାମସବୁ [[Special:Log/import|ଆମଦାନି ଇତିହାସ]]ରେ ସାଇଟ ହୋଇ ରହିଛି ।",
"import-interwiki-sourcewiki": "ମୂଳ ଉଇକି:",
"import-interwiki-sourcepage": "ମୂଳ ପୃଷ୍ଠା:",
"createacct-yourpasswordagain-ph": "Wprowadź hasło jeszcze raz",
"userlogin-remembermypassword": "Nie wylogowuj mnie",
"userlogin-signwithsecure": "Użyj bezpiecznego połączenia",
+ "cannotlogin-title": "Nie można się zalogować",
+ "cannotlogin-text": "Logowanie nie jest możliwe.",
"cannotloginnow-title": "W tej chwili nie można się teraz zalogować",
"cannotloginnow-text": "Podczas korzystania z $1 nie można się zalogować.",
+ "cannotcreateaccount-title": "Nie można utworzyć kont",
+ "cannotcreateaccount-text": "Bezpośrednie tworzenie konta nie jest włączone na tej wiki.",
"yourdomainname": "Twoja domena:",
"password-change-forbidden": "Nie można zmieniać haseł na tej wiki.",
"externaldberror": "Wystąpił błąd autentyfikacyjnej bazy danych lub nie posiadasz uprawnień koniecznych do aktualizacji zewnętrznego konta.",
"file-thumbnail-no": "Nazwa pliku zaczyna się od <strong>$1</strong>.\nWydaje się, że jest to pomniejszona grafika ''(miniaturka)''.\nJeśli posiadasz tę grafikę w pełnym rozmiarze – prześlij ją. Jeśli chcesz wysłać tę – zmień nazwę przesyłanego obecnie pliku.",
"fileexists-forbidden": "Plik o tej nazwie już istnieje i nie może zostać nadpisany.\nJeśli chcesz przesłać plik cofnij się i prześlij go pod inną nazwą. [[File:$1|thumb|center|$1]]",
"fileexists-shared-forbidden": "Plik o tej nazwie już istnieje we współdzielonym repozytorium plików.\nCofnij się i załaduj plik pod inną nazwą. [[File:$1|thumb|center|$1]]",
+ "fileexists-duplicate-version": "{{PLURAL:$2|Przesłany plik jest dokładną kopią starszej wersji pliku|Przesłane pliki są dokładnymi kopiami starszych wersji plików}} <strong>[[:$1]]</strong>.",
"file-exists-duplicate": "Ten plik jest kopią {{PLURAL:$1|pliku|następujących plików}}:",
"file-deleted-duplicate": "Identyczny do tego plik ([[:$1]]) został wcześniej usunięty.\nSprawdź historię usunięć tamtego pliku zanim prześlesz go ponownie.",
"file-deleted-duplicate-notitle": "Plik jest identyczny z plikiem, który został wcześniej usunięty, a jego nazwa została ukryta. Należy poprosić kogoś z możliwością przeglądania ukrytych danych, aby przeanalizował sytuację przed przystąpieniem do jego ponownego przesłania.",
"undeletedrevisions": "odtworzono {{PLURAL:$1|1 wersję|$1 wersje|$1 wersji}}",
"undeletedrevisions-files": "odtworzono $1 {{PLURAL:$1|wersję|wersje|wersji}} i $2 {{PLURAL:$2|plik|pliki|plików}}",
"undeletedfiles": "odtworzył $1 {{PLURAL:$1|plik|pliki|plików}}",
- "cannotundelete": "Odtworzenie nie powiodło się:\n$1",
+ "cannotundelete": "Niektóre lub wszystkie odtworzenia nie powiodły się:\n$1",
"undeletedpage": "'''Odtworzono stronę $1.'''\n\nZobacz [[Special:Log/delete|rejestr usunięć]], jeśli chcesz przejrzeć ostatnie operacje usuwania i odtwarzania stron.",
"undelete-header": "Zobacz [[Special:Log/delete|rejestr usunięć]], aby sprawdzić ostatnio usunięte strony.",
"undelete-search-title": "Przeszukiwanie usuniętych stron",
"pageinfo-article-id": "Identyfikator strony",
"pageinfo-language": "Język zawartości strony",
"pageinfo-content-model": "Model zawartości",
+ "pageinfo-content-model-change": "zmień",
"pageinfo-robot-policy": "Indeksowanie przez roboty",
"pageinfo-robot-index": "Dozwolone",
"pageinfo-robot-noindex": "Niedozwolone",
"yourpasswordagain": "پټنوم بيا وليکه",
"createacct-yourpasswordagain": "پټنوم مو تاييد کړۍ",
"createacct-yourpasswordagain-ph": "پټنوم مو بيا وټاپئ",
- "remembermypassword": "زما پټنوم په دې کمپيوټر (تر $1 {{PLURAL:$1|ورځې|ورځو}}) په ياد وساته!",
"userlogin-remembermypassword": "غونډال کې مې ننوتلی وساته",
"userlogin-signwithsecure": "خوندي اړيکتيا کارول",
+ "cannotcreateaccount-title": "گڼونونه نه شي جوړېدای",
"yourdomainname": "ستاسې شپول:",
"password-change-forbidden": "تاسې په دې ويکي باندې خپل پټنوم نه شی بدلولی.",
"login": "ننوتل",
"passwordreset-emailtitle": "د {{SITENAME}} د گڼون څرگندنې",
"passwordreset-emailelement": "کارن-نوم: \n$1\n\nلنډمهاله پټنوم: \n$2",
"passwordreset-emailsentemail": "د پټنوم بيا پرځای کېدنې لپاره برېښليک درولېږل شو.",
- "passwordreset-emailsent-capture": "د پټنوم بياپرځای کېدنې لپار مو يو برېښليک درولېږه، برېښليک په لاندې توگه ښودل شوی.",
"passwordreset-invalideamil": "ناسمه برېښليک پته",
"changeemail": "برېښليک پته بدلول يا ليرې کول",
"changeemail-header": "د گڼون برېښليک پته بدلول",
"post-expand-template-argument-warning": "'''گواښنه:''' دا مخ لږ تر لږه د يوې کينډۍ عاملين لري چې بې حده لوی دی.\nدا عاملين ړنگ شول.",
"post-expand-template-argument-category": "هغه مخونه چې د کينډۍ ړنگ شوي عاملين لري.",
"undo-norev": "دا سمون ناکړل کېدای نه شي دا ځکه چې دا سمون نشته او يا هم ړنگ شوی.",
- "cantcreateaccounttitle": "گڼون نه شي جوړېدای",
"viewpagelogs": "د دې مخ يادښتونه کتل",
"nohistory": "ددې مخ د سمون کوم پېښليک نه شته.",
"currentrev": "اوسنۍ بڼه",
"pageinfo-article-id": "د مخ پېژند",
"pageinfo-language": "د مخ د مېنځپانگې ژبه",
"pageinfo-content-model": "د مخ مېنځپانگې جوړښت",
+ "pageinfo-content-model-change": "بدلول",
"pageinfo-robot-policy": "ليکلړ اوډنه د روباټونو لخوا",
"pageinfo-robot-index": "پرېښل",
"pageinfo-robot-noindex": "ناپرېښل",
"createacct-yourpasswordagain-ph": "Digite a senha novamente",
"userlogin-remembermypassword": "Mantenha-me conectado",
"userlogin-signwithsecure": "Use a conexão segura",
+ "cannotlogin-title": "Não é possível entrar com sua conta",
+ "cannotlogin-text": "Não é possível conectar-se.",
"cannotloginnow-title": "Não é possível iniciar a sessão agora",
"cannotloginnow-text": "Não é possível autenticar usando $1.",
+ "cannotcreateaccount-title": "Não é possível criar uma conta",
+ "cannotcreateaccount-text": "A criação direta de contas não está habilitada nessa wiki.",
"yourdomainname": "Seu domínio:",
"password-change-forbidden": "Você não pode alterar senhas nessa wiki.",
"externaldberror": "Ocorreu ou um erro no banco de dados durante a autenticação ou não lhe é permitido atualizar a sua conta externa.",
"botpasswords-updated-body": "A senha de robô para o robô de nome \"$1\" do usuário \"$2\" foi atualizada.",
"botpasswords-deleted-title": "Senha de bot apagada",
"botpasswords-deleted-body": "A senha de robô para o robô de nome \"$1\" do usuário \"$2\" foi apagada.",
- "botpasswords-newpassword": "A nova senha para se autenticar com <strong>$1</strong> é <strong>$2</strong>. <em>Por favor, guarde isto para referência futura.",
+ "botpasswords-newpassword": "A nova senha para se autenticar com <strong>$1</strong> é <strong>$2</strong>. <em>Por favor, guarde isto para referência futura.</em> <br> (para bots antigos que requisitam que o nome da conta seja o mesmo que o eventual nome de usuário, Você também pode usar <strong>$3</strong>como nome de usuário e <strong>$4</strong> como senha.)",
"botpasswords-no-provider": "BotPasswordsSessionProvider não está disponível.",
"botpasswords-restriction-failed": "Restrições de senha de robô evitam esta autenticação.",
"botpasswords-invalid-name": "O nome de usuário especificado não contém o separador de senha de robô (\"$1\").",
"invalid-content-data": "Dados de conteúdo inválidos",
"content-not-allowed-here": "Conteúdo do tipo \"$1\" não é permitido na página [[$2]]",
"editwarning-warning": "Abandonar esta página pode fazer com que você perca todas as alterações que fez.\nSe você estiver autenticado, você pode desabilitar este aviso na seção {{int:prefs-editing}}\" de suas preferências.",
+ "editpage-invalidcontentmodel-title": "Modelo do conteúdo não suportado",
+ "editpage-invalidcontentmodel-text": "O modelo do conteúdo \"$1\" não é suportado.",
"editpage-notsupportedcontentformat-title": "Formato do conteúdo não suportado",
"editpage-notsupportedcontentformat-text": "O formato de conteúdo $1 não é suportando pelo modelo de conteúdo $2.",
"content-model-wikitext": "wikitexto",
"file-thumbnail-no": "O nome do arquivo começa com <strong>$1</strong>.\nIsso faz parecer se tratar de uma imagem de tamanho reduzido (''miniatura'', ou ''thumbnail'').\nSe você tem esta imagem em sua resolução completa, envie-a no lugar desta. Caso contrário, altere o nome de arquivo.",
"fileexists-forbidden": "Já existe um arquivo com este nome e ele não pode ser sobrescrito.\nSe ainda pretende enviar seu arquivo, volte e use um novo nome.\n[[File:$1|thumb|center|$1]]",
"fileexists-shared-forbidden": "Já existe um arquivo com este nome no repositório de arquivos compartilhados.\nSe você ainda quer enviar seu arquivo, volte e use um novo nome.\n[[File:$1|thumb|center|$1]]",
+ "fileexists-no-change": "O arquivo carregado é uma duplicata exata da versão atual de <strong>[[:$1]]</strong>.",
"file-exists-duplicate": "Este arquivo é uma duplicata do seguinte {{PLURAL:$1|arquivo|arquivos}}:",
"file-deleted-duplicate": "Um arquivo idêntico a este ([[:$1]]) foi eliminado anteriormente.\nVerifique o histórico de eliminação de tal arquivo antes de tentar re-enviar.",
"file-deleted-duplicate-notitle": "Um arquivo idêntico a este foi anteriormente excluído, e o título foi suprimido. Você deve comunicar com alguém capaz de visualizar dados suprimidos, para verificar a situação antes de enviá-lo novamente.",
"Josep Maria Roca Peña",
"Luan",
"Gato Preto",
- "Jdforrester"
+ "Jdforrester",
+ "Mansil"
]
},
"tog-underline": "Sublinhar ligações:",
"botpasswords-newpassword": "A nova palavra-passe para iniciar sessão com <strong>$1</strong> é <strong>$2</strong>. Por favor, recorde-se dela para futura referência.</em>",
"botpasswords-no-provider": "BotPasswordsSessionProvider não está disponível.",
"botpasswords-restriction-failed": "Restrições de senha de robô evitam esta autenticação.",
- "botpasswords-invalid-name": "O nome de usuário especificado não contém o separador de senha de robô (\"$1\").",
+ "botpasswords-invalid-name": "O nome de utilizador especificado não contém o separador de palavra-passe de robô (\"$1\").",
"botpasswords-not-exist": "O usuário \"$1\" não possui uma senha de robô \"$2\".",
"resetpass_forbidden": "Não é possível alterar palavras-passe",
"resetpass_forbidden-reason": "As palavras-passe não podem ser alteradas: $1",
"passwordreset-emailerror-capture2": "O envio do correio {{GENDER:$2|ao usuário|à usuária}} falhou: $1 {{PLURAL:$3|O nome de usuário e senha são mostradas abaixo|A lista de nomes de usuários e senhas é mostrada abaixo}}.",
"passwordreset-nocaller": "Um interlocutor deve ser fornecido",
"passwordreset-nosuchcaller": "A pessoa que chama não existe: $1",
- "passwordreset-ignored": "A redefinição de senha não foi realizada. Talvez o provedor não tenha sido configurado, sim?",
+ "passwordreset-ignored": "A reposição de palavra-passe não foi realizada. Talvez não tenha sido configurado o provedor?",
"passwordreset-invalideamil": "Correio eletrónico inválido",
"passwordreset-nodata": "Não foram fornecidos nome de utilizador(a) nem endereço de correio eletrónico",
"changeemail": "Alterar ou remover o endereço de correio eletrónico",
"apisandbox-loading-results": "A receber resultados da API...",
"apisandbox-request-url-label": "URL do pedido:",
"apisandbox-request-time": "Tempo de processamento: {{PLURAL:$1|$1 ms}}",
- "apisandbox-results-fixtoken": "Corrija o identificador e envie-o novamente",
- "apisandbox-results-fixtoken-fail": "Não foi possível recuperar o identificador \"$1\".",
+ "apisandbox-results-fixtoken": "Corrija o identificador e volte a submete-lo",
+ "apisandbox-results-fixtoken-fail": "Não foi possível obter o identificador \"$1\".",
"apisandbox-alert-page": "Os campos nesta página não são válidos.",
"apisandbox-alert-field": "O valor deste campo não é válido.",
"booksources": "Fontes bibliográficas",
"authpage-cannot-login-continue": "Não é possível continuar a iniciar sessão. A sua sessão pode ter expirado.",
"authpage-cannot-create": "Não é possível iniciar a criação da conta.",
"authpage-cannot-create-continue": "Não é possível continuar a criação da conta. A sua sessão pode ter expirado.",
- "authpage-cannot-link": "Não se pode iniciar a vinculação da conta.",
+ "authpage-cannot-link": "Não é possível iniciar a associação da conta.",
"authpage-cannot-link-continue": "Não é possível continuar a criação da conta. A sua sessão pode ter expirado.",
"cannotauth-not-allowed-title": "Permissão negada",
"cannotauth-not-allowed": "Não possui permissão para utilizar esta página",
"createacct-yourpasswordagain-ph": "Placeholder text in create account form for re-enter password field.\n\nSee example: [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]",
"userlogin-remembermypassword": "The text for a check box in [[Special:UserLogin]].",
"userlogin-signwithsecure": "Text of link to HTTPS login form.\n\nSee example: [[Special:UserLogin]]",
- "cannotloginnow-title": "Error page title shown when logging in is not possible.",
- "cannotloginnow-text": "Error page text shown when logging in is not possible. Parameters:\n* $1 - Session type in use that makes it not possible to log in, from a message like {{msg-mw|sessionprovider-mediawiki-session-cookiesessionprovider}}.",
+ "cannotlogin-title": "Error page title shown when logging in is not possible. This is a catch-all when a more specific reason is not available.",
+ "cannotlogin-text": "Error page text shown when logging in is not possible. This is a catch-all when a more specific reason is not available.",
+ "cannotloginnow-title": "Error page title shown when logging in is not possible becuse the session provider in use does the user authentication itself.",
+ "cannotloginnow-text": "Error page text shown when logging in is not possible becuse the session provider in use does the user authentication itself. Parameters:\n* $1 - Session type in use that makes it not possible to log in, from a message like {{msg-mw|sessionprovider-mediawiki-session-cookiesessionprovider}}.",
+ "cannotcreateaccount-title": "Error page title shown when manual account creation is not possible. That probably means the wiki supports some other account creation method, e.g. autocreation by the session provider. (When account creation is possible but the current user does not have permission to do it, a more specific error message is displayed.)",
+ "cannotcreateaccount-text": "Error page text shown when manual account creation is not possible. That probably means the wiki supports some other account creation method, e.g. autocreation by the session provider. (When account creation is possible but the current user does not have permission to do it, a more specific error message is displayed.)",
"yourdomainname": "Used as label for listbox.",
"password-change-forbidden": "Error message shown when an external authentication source does not allow the password to be changed.",
"externaldberror": "This message is thrown when a valid attempt to change the wiki password for a user fails because of a database error or an error from an external system.",
"botpasswords-updated-body": "Success message when a bot password is updated. Parameters:\n* $1 - Bot name\n* $2 - User name",
"botpasswords-deleted-title": "Title of the success page when a bot password is deleted.",
"botpasswords-deleted-body": "Success message when a bot password is deleted. Parameters:\n* $1 - Bot name\n* $2 - User name",
- "botpasswords-newpassword": "Success message to display the new password when a bot password is created or updated. Parameters:\n* $1 - User name to be used for login.\n* $2 - Password to be used for login.",
+ "botpasswords-newpassword": "Success message to display the new password when a bot password is created or updated. Parameters:\n* $1 - User name to be used for login.\n* $2 - Password to be used for login.\n* $3, $4 - an alternative version of the user name and password, respectively, which is less preferred, but more compatible with old bots.",
"botpasswords-no-provider": "Error message when login is attempted but the BotPasswordsSessionProvider is not included in <code>$wgSessionProviders</code>.",
"botpasswords-restriction-failed": "Error message when login is rejected because the configured restrictions were not satisfied.",
"botpasswords-invalid-name": "Error message when a username lacking the separator character is passed to BotPassword. Parameters:\n* $1 - The separator character.",
"invalid-content-data": "Error message indicating that the page's content can not be saved because it is invalid. This may occurr for content types with internal consistency constraints.",
"content-not-allowed-here": "Error message indicating that the desired content model is not supported in given localtion.\n* $1 - the human readable name of the content model: {{msg-mw|Content-model-wikitext}}, {{msg-mw|Content-model-javascript}}, {{msg-mw|Content-model-css}} or {{msg-mw|Content-model-text}}\n* $2 - the title of the page in question",
"editwarning-warning": "Uses {{msg-mw|Prefs-editing}}",
+ "editpage-invalidcontentmodel-title": "Title of error page shown when using an unrecognized content model on EditPage",
+ "editpage-invalidcontentmodel-text": "Error message shown when using an unrecognized content model on EditPage. $1 is the user's invalid input",
"editpage-notsupportedcontentformat-title": "Title of error page shown when using an incompatible format on EditPage.\n\nUsed as title for the following error message:\n* {{msg-mw|Editpage-notsupportedcontentformat-text}}.",
"editpage-notsupportedcontentformat-text": "Error message shown when using an incompatible format on EditPage.\n\nThe title for this error is {{msg-mw|Editpage-notsupportedcontentformat-title}}.\n\nParameters:\n* $1 - the format id\n* $2 - the content model name",
"content-model-wikitext": "Name for the wikitext content model, used when decribing what type of content a page contains.\n\nThis message is substituted in:\n*{{msg-mw|Bad-target-model}}\n*{{msg-mw|Content-not-allowed-here}}",
"pageinfo-article-id": "The numeric identifier of the page.\n{{Identical|Page ID}}",
"pageinfo-language": "Language in which the page content is written.",
"pageinfo-content-model": "The model in which the page content is written.\n\nUsed as label at [{{fullurl:Main Page|action=info}} action=info]. Followed by one of the following messages:\n* {{msg-mw|Content-model-wikitext}}\n* {{msg-mw|Content-model-javascript}}\n* {{msg-mw|Content-model-css}}\n* {{msg-mw|Content-model-text}}",
- "pageinfo-content-model-change": "Link text for a link to Special:ChangeContentModel. The link will be wrapped in parenthesis.",
+ "pageinfo-content-model-change": "Link text for a link to Special:ChangeContentModel. The link will be wrapped in parenthesis.\n{{Identical|Change}}",
"pageinfo-robot-policy": "The search engine status of the page.\n\nUsed as label. Followed by any one of the following messages:\n*{{msg-mw|Pageinfo-robot-index}}\n*{{msg-mw|Pageinfo-robot-noindex}}",
"pageinfo-robot-index": "An indication that the page is indexable by search engines, that is listed in their search results.\n\nPreceded by the label {{msg-mw|Pageinfo-robot-policy}}.\n{{Identical|Allowed}}",
"pageinfo-robot-noindex": "An indication that the page is not indexable (that is, is not listed on the results page of a search engine).\n\nPreceded by the label {{msg-mw|Pageinfo-robot-policy}}.",
"tag-filter": "Caption of a filter shown on lists of changes (e.g. [[Special:Log]], [[Special:Contributions]], [[Special:Newpages]], [[Special:Recentchanges]], [[Special:Recentchangeslinked]], page histories)",
"tag-filter-submit": "Caption of the submit button displayed next to the tag filter on lists of changes (e.g. [[Special:Log]], [[Special:Contributions]], [[Special:Newpages]], [[Special:Recentchanges]], [[Special:Recentchangeslinked]], page histories)\n\n{{Identical|Filter}}",
"tag-list-wrapper": "Wrapper for the list of tags shown on recent changes, watchlists, history pages and diffs.\n\nParameters:\n* $1 - number of distinct tags for given edit\n* $2 - comma-separated list of tags for given edit",
+ "tag-mw-contentmodelchange": "Change tag for edits that change the content model of a page",
+ "tag-mw-contentmodelchange-description": "Description for \"content model change\" change tag",
"tags-title": "The title of [[Special:Tags]].\n{{Identical|Tag}}",
"tags-intro": "Explanation on top of [[Special:Tags]]. For more information on tags see [[mw:Manual:Tags|MediaWiki]].",
"tags-tag": "Caption of a column in [[Special:Tags]]. For more information on tags see [[mw:Manual:Tags|MediaWiki]].",
"tags-actions-header": "Caption of a column in [[Special:Tags]]. The column contains action links like \"delete\". For more information on tags see [[mw:Manual:Tags|MediaWiki]].\n{{Identical|Action}}",
"tags-active-yes": "Table cell contents if given tag is \"active\".\n\nSee also:\n* {{msg-mw|Tags-active-no}}\n{{Identical|Yes}}",
"tags-active-no": "Table cell contents if given tag is not \"active\".\n\nSee also:\n* {{msg-mw|Tags-active-yes}}\n{{Identical|No}}",
- "tags-source-extension": "Table cell contents if given tag can be applied automatically by a software [[mw:Manual:Extensions|extension]].\n\nSee also:\n* {{msg-mw|Tags-source-manual}}\n* {{msg-mw|Tags-source-none}}",
+ "tags-source-extension": "Table cell contents if given tag can be applied automatically by the MediaWiki software.\n\nSee also:\n* {{msg-mw|Tags-source-manual}}\n* {{msg-mw|Tags-source-none}}",
"tags-source-manual": "\"Applied\" is not past tense, but an adjective that describes an action that sometimes happens, as in the sentence: \"(this tag is usually) applied by users and bots\".\n\nTable cell contents if given tag can be applied by users or bots.\n\nSee also:\n* {{msg-mw|Tags-source-extension}}\n* {{msg-mw|Tags-source-none}}",
"tags-source-none": "Table cell contents if given tag is no longer in use. (It was applied in the past, but it is currently not applied.)\n\nSee also:\n* {{msg-mw|Tags-source-extension}}\n* {{msg-mw|Tags-source-manual}}",
"tags-edit": "Used on [[Special:Tags]]. Verb. Used as display text on a link to create/edit a description.\n{{Identical|Edit}}",
"createacct-yourpasswordagain-ph": "Introduceți parola din nou",
"userlogin-remembermypassword": "Păstrează-mă autentificat",
"userlogin-signwithsecure": "Utilizează conexiunea securizată",
+ "cannotlogin-title": "Imposibil de autentificat",
+ "cannotlogin-text": "Autentificarea nu este posibilă.",
"cannotloginnow-title": "Nu se poate conecta acum",
"cannotloginnow-text": "Conectarea nu este posibilă când se utilizează $1.",
+ "cannotcreateaccount-title": "Imposibil de creat conturi",
+ "cannotcreateaccount-text": "Crearea directă de conturi nu este activată pe acest wiki.",
"yourdomainname": "Domeniul dumneavoastră:",
"password-change-forbidden": "Nu puteți schimba parole pe acest wiki.",
"externaldberror": "A fost fie o eroare de bază de date pentru o autentificare extenă sau nu aveți permisiunea să actualizați contul extern.",
"rightslogtext": "Acest jurnal cuprinde modificările permisiunilor utilizatorilor.",
"action-read": "citiți această pagină",
"action-edit": "modificați această pagină",
- "action-createpage": "creați pagini",
- "action-createtalk": "creați pagini de discuție",
+ "action-createpage": "creați această pagină",
+ "action-createtalk": "creați această pagină de discuție",
"action-createaccount": "creați acest cont de utilizator",
"action-history": "vizualizați istoricul acestei pagini",
"action-minoredit": "marcați această modificare ca minoră",
"watchlistedit-raw-done": "Lista paginilor urmărite a fost actualizată.",
"watchlistedit-raw-added": "{{PLURAL:$1|1 titlu a fost adăugat|$1 titluri au fost adăugate}}:",
"watchlistedit-raw-removed": "{{PLURAL:$1|1 titlu a fost șters|$1 titluri au fost șterse}}:",
- "watchlistedit-clear-title": "Listă de pagini urmărite golită",
+ "watchlistedit-clear-title": "Golire listă de pagini urmărite",
"watchlistedit-clear-legend": "Golire listă de pagini urmărite",
"watchlistedit-clear-explain": "Toate titlurile vor fi înlăturate din lista dumnevoastră de pagini urmărite",
"watchlistedit-clear-titles": "Titluri:",
"createacct-yourpasswordagain-ph": "Введите пароль еще раз",
"userlogin-remembermypassword": "Оставаться в системе",
"userlogin-signwithsecure": "Защищённое соединение",
+ "cannotlogin-title": "Невозможно войти",
+ "cannotlogin-text": "Вход в систему невозможен.",
"cannotloginnow-title": "Невозможно войти прямо сейчас",
"cannotloginnow-text": "Нельзя войти во время использования $1.",
+ "cannotcreateaccount-title": "Невозможно создать учётные записи",
+ "cannotcreateaccount-text": "Прямое создание учетных записей не включено в этой вики.",
"yourdomainname": "Ваш домен:",
"password-change-forbidden": "Вы не можете изменить пароль в этой вики.",
"externaldberror": "Произошла ошибка при аутентификации с помощью внешней базы данных или у вас недостаточно прав для внесения изменений в свою внешнюю учётную запись.",
"botpasswords-updated-body": "Пароль бота для бота «$1» участника «$2» был обновлён.",
"botpasswords-deleted-title": "Пароль бота удалён",
"botpasswords-deleted-body": "Пароль бота для бота «$1» участника «$2» был удалён.",
- "botpasswords-newpassword": "Новый пароль для входа под <strong>$1</strong> — <strong>$2</strong>. <em>Запишите его для последующего использования.</em>",
+ "botpasswords-newpassword": "Новый пароль для входа под <strong>$1</strong> — <strong>$2</strong>. <em>Запишите его для последующего использования.</em> <br /> (Для старых ботов, которые требуют, чтоб логин участника был таким же, как имя потенциального участника, вы можете также использовать <strong>$3</strong> как имя участника и <strong>$4</strong> в качестве пароля.)",
"botpasswords-no-provider": "BotPasswordsSessionProvider недоступен.",
"botpasswords-restriction-failed": "Из-за ограничений, связанных с паролем бота, вход не произведён.",
"botpasswords-invalid-name": "Указанное имя участника не содержит разделителя для пароля бота («$1»).",
"invalid-content-data": "Недопустимые данные",
"content-not-allowed-here": "Содержимое \"$1\" недопустимо на странице [[$2]]",
"editwarning-warning": "Переход на другую страницу может привести к потере внесённых вами изменений.\nЕсли вы зарегистрированы в системе, то вы можете отключить это предупреждение в разделе «{{int:prefs-editing}}» ваших настроек.",
+ "editpage-invalidcontentmodel-title": "Модель содержимого не поддерживается",
+ "editpage-invalidcontentmodel-text": "Модель содержимого «$1» не поддерживается.",
"editpage-notsupportedcontentformat-title": "Формат содержимого не поддерживается",
"editpage-notsupportedcontentformat-text": "Формат содержимого $1 не поддерживается моделью содержимого $2.",
"content-model-wikitext": "вики-текст",
"pageinfo-article-id": "Идентификатор страницы",
"pageinfo-language": "Язык страницы",
"pageinfo-content-model": "Модель содержимого страницы",
+ "pageinfo-content-model-change": "изменить",
"pageinfo-robot-policy": "Индексация поисковыми роботами",
"pageinfo-robot-index": "Разрешено",
"pageinfo-robot-noindex": "Не разрешено",
"tags-actions-header": "Действия",
"tags-active-yes": "Да",
"tags-active-no": "Нет",
- "tags-source-extension": "Определяется расширением",
+ "tags-source-extension": "Определяется программным обеспечением",
"tags-source-manual": "Вносятся вручную участниками и ботами",
"tags-source-none": "Больше не используется",
"tags-edit": "править",
"tog-numberheadings": "Automaticky číslovať nadpisy",
"tog-showtoolbar": "Zobraziť panel nástrojov úprav",
"tog-editondblclick": "Upravovať stránky po dvojitom kliknutí",
- "tog-editsectiononrightclick": "Umožniť upravovanie sekcie pravým kliknutím na nadpisy sekcií",
+ "tog-editsectiononrightclick": "Umožniť upravovanie sekcie kliknutím pravým tlačidlom myši na nadpisy sekcií",
"tog-watchcreations": "Pridávať stránky, ktoré vytvorím a súbory, ktoré nahrám medzi sledované",
"tog-watchdefault": "Pridávať stránky a súbory, ktoré upravím medzi sledované",
"tog-watchmoves": "Pridávať stránky a súbory, ktoré presuniem medzi sledované",
"tog-watchdeletion": "Pridávať stránky a súbory, ktoré zmažem medzi sledované",
- "tog-watchrollback": "Pridať stránky na ktorých som použil rollback do môjho zoznamu sledovaných stránok",
+ "tog-watchrollback": "Pridať do môjho zoznamu sledovaných stránok stránky, na ktorých som použil vrátenie",
"tog-minordefault": "Označovať všetky zmeny štandardne ako drobné",
"tog-previewontop": "Zobrazovať náhľad pred textovým poľom úprav, nie až za ním",
"tog-previewonfirst": "Zobraziť náhľad pred prvou úpravou",
"tog-enotifwatchlistpages": "Upozorniť ma e-mailom, keď sa zmení stránka alebo súbor z môjho zoznamu sledovaných",
"tog-enotifusertalkpages": "Upozorniť ma e-mailom po zmene mojej používateľskej diskusnej stránky",
"tog-enotifminoredits": "Upozorniť ma e-mailom aj na drobné úpravy stránok a súborov",
- "tog-enotifrevealaddr": "Zobraziť moju mailovú adresu v notifikačných e-mailoch",
+ "tog-enotifrevealaddr": "Zobraziť moju emailovú adresu v emailoch s upozornením",
"tog-shownumberswatching": "Zobraziť počet používateľov sledujúcich stránku",
"tog-oldsig": "Súčasný podpis:",
"tog-fancysig": "Považovať podpisy za wikitext (bez automatických odkazov)",
"pool-timeout": "Bol prekročený vyhradený čas čakania na zámok",
"pool-queuefull": "Front je plný",
"pool-errorunknown": "Neznáma chyba",
- "pool-servererror": "Služba riadiaca prístup k serverom nieje dostupná ($1).",
+ "pool-servererror": "Služba riadiaca prístup k serverom nie je dostupná ($1).",
"poolcounter-usage-error": "Chyba použitia: $1",
"aboutsite": "O {{GRAMMAR:lokál|{{SITENAME}}}}",
"aboutpage": "Project:Úvod",
"toc": "Obsah",
"showtoc": "zobraziť",
"hidetoc": "skryť",
- "collapsible-collapse": "skry",
- "collapsible-expand": "rozbaľ",
+ "collapsible-collapse": "Skryť",
+ "collapsible-expand": "Rozbaliť",
"confirmable-confirm": "Ste si {{GENDER:$1|istý|istá|istí}}?",
"confirmable-yes": "Áno",
"confirmable-no": "Nie",
"nospecialpagetext": "<strong>Vyžiadali ste si neplatnú špeciálnu stránku.</strong>\n\nZoznam platných špeciálnych stránok nájdete na [[Special:SpecialPages|{{int:specialpages}}]].",
"error": "Chyba",
"databaseerror": "Chyba v databáze",
- "databaseerror-text": "Došlo k chybe pri otázke do databázy.\nMôže to byť spôsobené chybou v softvéri.",
+ "databaseerror-text": "Došlo k chybe pri kladení požiadavky do databázy.\nMôže to byť spôsobené chybou v softvéri.",
"databaseerror-textcl": "Vyskytla sa chyba dopytu do databázy.",
- "databaseerror-query": "Otázka: $1",
+ "databaseerror-query": "Požiadavka: $1",
"databaseerror-function": "Funkcia: $1",
"databaseerror-error": "Chyba: $1",
"laggedslavemode": "Upozornenie: Je možné, že stránka neobsahuje posledné aktualizácie.",
"filerenameerror": "Nebolo možné premenovať súbor „$1“ na „$2“.",
"filedeleteerror": "Nebolo možné vymazať súbor „$1“.",
"directorycreateerror": "Nebolo možné vytvoriť adresár „$1“.",
- "directoryreadonlyerror": "Adresár \"$1\" je iba na čítanie.",
- "directorynotreadableerror": "Adresár \"$1\" sa nedá čítať.",
+ "directoryreadonlyerror": "Adresár „$1“ je iba na čítanie.",
+ "directorynotreadableerror": "Adresár „$1“ nie je možné čítať.",
"filenotfound": "Nebolo možné nájsť súbor „$1“.",
"unexpected": "Neočakávaná hodnota: „$1“=„$2“.",
"formerror": "Chyba: nepodarilo sa odoslať formulár",
"viewsourcetext": "Môžete si zobraziť a kopírovať zdroj tejto stránky:",
"viewyourtext": "Môžete si prehliadnuť a skopírovať zdrojový kód <strong>vašich úprav</strong> tejto stránky:",
"protectedinterface": "Táto stránka poskytuje text používateľského rozhrania tejto wiki a je zamknutá, aby sa predišlo jej zneužitiu.\nAk chcete pridať alebo zmeniť preklady pre všetky wiki, prosím, použite [https://translatewiki.net/ translatewiki.net], projekt lokalizácie MediaWiki.",
- "editinginterface": "'''Upozornenie:''' Upravujete stránku, ktorá poskytuje text používateľského rozhrania.\nZmeny tejto stránky ovplyvnia vzhľad používateľského rozhrania ostatným používateľom.\nAk chcete pridať alebo zmeniť preklady pre všetky wiki, prosím, použite [https://translatewiki.net/ translatewiki.net], projekt lokalizácie MediaWiki.",
+ "editinginterface": "<strong>Upozornenie:</strong> Upravujete stránku, ktorá poskytuje text používateľského rozhrania.\nZmeny tejto stránky ovplyvnia vzhľad používateľského rozhrania ostatným používateľom.\nAk chcete pridať alebo zmeniť preklady pre všetky wiki, prosím, použite [https://translatewiki.net/ translatewiki.net], projekt lokalizácie MediaWiki.",
"translateinterface": "Na pridanie a zmeny prekladov pre všetky wiki použite [https://translatewiki.net/ translatewiki.net], projekt na lokalizáciu MediaWiki.",
"cascadeprotected": "Táto stránka bola zamknutá proti úpravám, pretože je použitá na {{PLURAL:$1|nasledovnej stránke, ktorá je zamknutá|nasledovných stránkach, ktoré sú zamknuté}} voľbou „kaskádového zamknutia“:\n$2",
"namespaceprotected": "Nemáte povolenie upravovať stránky v mennom priestore '''$1'''.",
"invalidtitle-unknownnamespace": "Neplatný názov s neznámym číslom menného priestoru „$1“ a textom „$2“",
"exception-nologin": "Nie ste prihlásený",
"exception-nologin-text": "Táto stránka alebo operácia vyžaduje, aby ste boli prihlásený.",
- "exception-nologin-text-manual": "Pre prístup na túto stránku alebo k tejto akcii sa musíte $1.",
+ "exception-nologin-text-manual": "Pre prístup na túto stránku alebo k tejto operácii sa musíte $1.",
"virus-badscanner": "Chybná konfigurácia: neznámy antivírus: ''$1''",
"virus-scanfailed": "kontrola zlyhala (kód $1)",
"virus-unknownscanner": "neznámy antivírus:",
- "logouttext": "'''Práve ste sa odhlásili.'''\n\nUvedomte si, že niektoré stránky sa môžu naďalej zobrazovať ako keby ste boli prihlásený, až kým nevymažete vyrovnávaciu pamäť vášho prehliadača.",
+ "logouttext": "<strong>Práve ste sa odhlásili.</strong>\n\nUvedomte si, že niektoré stránky sa môžu naďalej zobrazovať ako keby ste boli prihlásený, až kým nevymažete vyrovnávaciu pamäť vášho prehliadača.",
"welcomeuser": "Vitajte, $1 !",
"welcomecreation-msg": "Váš účet bol vytvorený.\nNezabudnite zmeniť svoje [[Special:Preferences|Predvoľby {{GRAMMAR:genitív|{{SITENAME}}}}]].",
"yourname": "Používateľské meno:",
"yourpasswordagain": "Zopakujte heslo:",
"createacct-yourpasswordagain": "Potvrdiť heslo",
"createacct-yourpasswordagain-ph": "Zadajte heslo znova",
- "userlogin-remembermypassword": "Zapamätať si ma",
+ "userlogin-remembermypassword": "Zapamätať si moje prihlásenie",
"userlogin-signwithsecure": "Použiť zabezpečené pripojenie",
"yourdomainname": "Vaša doména:",
"password-change-forbidden": "Na tejto wiki si nemôžete zmeniť heslo.",
"userlogin-resetlink": "Zabudli ste svoje prihlasovacie údaje?",
"userlogin-resetpassword-link": "Zabudli ste heslo?",
"userlogin-helplink2": "Pomoc s prihlásením",
- "userlogin-loggedin": "Ste už {{GENDER:$1|prihĺasený|prihlásená}} ako $1.\nPomocou formulára nižšie sa môžete prihlásiť ako iný redaktor.",
+ "userlogin-loggedin": "Ste už {{GENDER:$1|prihlasený|prihlásená}} ako $1.\nPomocou formulára nižšie sa môžete prihlásiť ako iný používateľ.",
"userlogin-reauth": "Aby ste preukázali, že ste $1, musíte sa znovu prihlásiť.",
"userlogin-createanother": "Vytvoriť ďalší účet",
- "createacct-emailrequired": "E-mailová adresa",
- "createacct-emailoptional": "E-mailová adresa (nepovinné)",
- "createacct-email-ph": "Zadajte vašu e-mailovú adresu",
- "createacct-another-email-ph": "Zadajte vašu e-mailovú adresu",
- "createaccountmail": "Použiť dočasné náhodné heslo a poslať ho na uvedenú e-mailovú adresu",
+ "createacct-emailrequired": "Emailová adresa",
+ "createacct-emailoptional": "Emailová adresa (nepovinné)",
+ "createacct-email-ph": "Zadajte svoju emailovú adresu",
+ "createacct-another-email-ph": "Zadajte svoju emailovú adresu",
+ "createaccountmail": "Použiť dočasné náhodné heslo a poslať ho na uvedenú emailovú adresu",
"createaccountmail-help": "Môže byť použité na vytvorenie účtu pre inú osobu bez prezradenia hesla.",
"createacct-realname": "Skutočné meno (nepovinné)",
"createaccountreason": "Dôvod:",
"createacct-reason": "Dôvod",
"createacct-reason-ph": "Prečo si vytvárate ďalší účet",
"createacct-reason-help": "Správa zobrazená v knihe nových používateľov",
- "createacct-submit": "Vytvoriť účet",
+ "createacct-submit": "Vytvoriť si účet",
"createacct-another-submit": "Vytvoriť účet",
- "createacct-continue-submit": "Pokračovať v zakladaní účtu",
- "createacct-another-continue-submit": "Pokračovať v zakladaní účtu",
- "createacct-benefit-heading": "{{GRAMMAR:akuzatív|{{SITENAME}}}} tvoria ľudia ako vy.",
+ "createacct-continue-submit": "Pokračovať vo vytváraní účtu",
+ "createacct-another-continue-submit": "Pokračovať vo vytváraní účtu",
+ "createacct-benefit-heading": "{{GRAMMAR:akuzatív|{{SITENAME}}}} tvoria ľudia ako ste vy.",
"createacct-benefit-body1": "{{PLURAL:$1|úprava|úpravy|úprav}}",
"createacct-benefit-body2": "{{PLURAL:$1|stránka|stránky|stránok}}",
"createacct-benefit-body3": "{{PLURAL:$1|nedávny prispievateľ|nedávni prispievatelia|nedávnych prispievateľov}}",
"badretype": "Zadané heslá nie sú rovnaké.",
- "usernameinprogress": "Vytváranie účtu s týmto menom už prebieha. Počkajte prosím.",
+ "usernameinprogress": "Vytváranie účtu s týmto menom už prebieha. Prosím, počkajte.",
"userexists": "Zadané používateľské meno sa už používa.\nProsím, zvoľte si iné meno.",
"loginerror": "Chyba pri prihlasovaní",
"createacct-error": "Chyba pri vytváraní účtu",
"passwordreset-emailsentemail": "Pokiaľ je toto e-mailová adresa zaregistrovaná k vášmu účtu, bude na ňu zaslaný e-mail pre získanie nového hesla.",
"passwordreset-emailsentusername": "Pokiaľ je príslušná mailová adresa zaregistrovaná, bude na ňu zaslaný e-mail s novým heslom.",
"changeemail": "Zmeniť alebo odstrániť e-mailovú adresu",
- "changeemail-header": "Zmena e-mailovej adresy pre účet",
+ "changeemail-header": "Vyplňte tento formulár, ak chcete zmeniť svoju emailovú adresu. Ak chcete odstrániť priradenie akejkoľvek emailovej adresy k vášmu účtu, nechajte pri odosielaní formulára emailovú adresu nevyplnenú",
"changeemail-no-info": "Na prístup k tejto stránke musíte byť prihlásený.",
"changeemail-oldemail": "Súčasná e-mailová adresa:",
"changeemail-newemail": "Nová e-mailová adresa:",
"missingsummary": "'''Pripomienka:''' Neposkytli ste zhrnutie úprav. Ak kliknete znova na Uložiť, vaše úpravy sa uložia bez zhrnutia úprav.",
"selfredirect": "<strong>Upozornenie:</strong> Snažíte sa túto stránku presmerovať samú na seba.\nMožno ste zadali chybný cieľ presmerovania, alebo editujete nesprávnu stránku.\nAk znova kliknete na „{{int:savearticle}}“, bude presmerovanie aj napriek tomu vytvorené.",
"missingcommenttext": "Prosím, dolu napíšte komentár.",
- "missingcommentheader": "'''Pripomienka:''' Neposkytli ste predmet/hlavičku tohto komentára.\nAk znova kliknete na tlačidlo „{{int:savearticle}}“, vaša úprava sa uloží bez nej.",
+ "missingcommentheader": "<strong>Pripomienka:</strong> Neposkytli ste predmet/hlavičku tohto komentára.\nAk znova kliknete na tlačidlo „{{int:savearticle}}“, vaša úprava sa uloží bez nej.",
"summary-preview": "Náhľad zhrnutia:",
- "subject-preview": "Náhľad predmetu/hlavičky:",
+ "subject-preview": "Náhľad predmetu:",
"previewerrortext": "Pri pokuse o zobrazenie náhľadu došlo k chybe.",
"blockedtitle": "Používateľ je zablokovaný",
"blockedtext": "'''Vaše používateľské meno alebo IP adresa bola zablokovaná.'''\n\nZablokoval vás správca $1. Udáva tento dôvod:<br />''$2''\n\n* Blokovanie začalo: $8\n* Blokovanie vyprší: $6\n* Kto mal byť zablokovaný: $7\n\nMôžete kontaktovať $1 alebo s jedného z ďalších [[{{MediaWiki:Grouppage-sysop}}|správcov]] a prediskutovať blokovanie.\nUvedomte si, že nemôžete použiť funkciu „{{int:Emailuser}}“, pokiaľ nemáte registrovanú platnú e-mailovú adresu vo svojich [[Special:Preferences|nastaveniach]].\nVaša IP adresa je $3 a ID blokovania je #$5.\nProsím, uveďte oba tieto údaje do každej správy, ktorú posielate.",
"accmailtext": "Náhodne vytvorené heslo pre používateľa [[User talk:$1|$1]] bolo poslané na $2. Je možné ho zmeniť na stránke ''[[Special:ChangePassword|zmena hesla]]'' po prihlásení.",
"newarticle": "(Nový)",
"newarticletext": "Sledovali ste odkaz na stránku, ktorá zatiaľ neexistuje.\nStránku vytvoríte tak, že začnete písať do poľa nižšie (viac informácií nájdete na stránkach [$1 nápovedy]).\nAk ste sa sem dostali nechtiac, kliknite na tlačidlo <strong>späť</strong> vo svojom prehliadači.",
- "anontalkpagetext": "----''Toto je diskusná stránka anonymného používateľa, ktorý nemá vytvorené svoje konto alebo ho nepoužíva.\nPreto musíme na jeho identifikáciu použiť numerickú IP adresu. Je možné, že takúto IP adresu používajú viacerí používatelia.\nAk ste anonymný používateľ a máte pocit, že vám boli adresované irelevantné diskusné príspevky, [[Special:CreateAccount|vytvorte si konto]] alebo sa [[Special:UserLogin|prihláste]], aby sa zamedzilo budúcim zámenám s inými anonymnými používateľmi.''",
+ "anontalkpagetext": "----\n<em>Toto je diskusná stránka anonymného používateľa, ktorý nemá vytvorené svoje konto alebo ho nepoužíva.</em>\nPreto musíme na jeho identifikáciu použiť numerickú IP adresu. Je možné, že takúto IP adresu používajú viacerí používatelia.\nAk ste anonymný používateľ a máte pocit, že vám boli adresované irelevantné diskusné príspevky, [[Special:CreateAccount|vytvorte si konto]] alebo sa [[Special:UserLogin|prihláste]], aby sa zamedzilo budúcim zámenám s inými anonymnými používateľmi.",
"noarticletext": "Na tejto stránke sa momentálne nenachádza žiadny text.\nMôžete [[Special:Search/{{PAGENAME}}|vyhľadávať názov tejto stránky]] v obsahu iných stránok,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} vyhľadávať v súvisiacich záznamoch] alebo [{{fullurl:{{FULLPAGENAME}}|action=edit}} vytvoriť túto stránku]</span>.",
"noarticletext-nopermission": "Táto stránka momentálne neobsahuje žiadny text.\nMôžete [[Special:Search/{{PAGENAME}}|hľadať názov tejto stránky]] v texte iných stránok\nalebo <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} hľadať v súvisiacich záznamoch]</span>, ale nemáte oprávnenie túto stránku vytvoriť.",
"missing-revision": "Revízia #$1 stránky s názvom „{{FULLPAGENAME}}“ neexistuje.\n\nPravdepodobne ste nasledovali zastaraný odkaz na historickú verziu stránky, ktorá bola medzičasom odstránená.\nPodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname zmazaní].",
"userpage-userdoesnotexist": "Používateľský účet „<nowiki>$1</nowiki>“ nie je registrovaný. Prosím, skontrolujte, či naozaj chcete vytvoriť/upravovať túto stránku.",
"userpage-userdoesnotexist-view": "Používateľský účet „$1“ nie je registrovaný.",
"blocked-notice-logextract": "Tento používateľ je momentálne zablokovaný.\nDolu je pre informáciu posledná položka zo záznamu blokovaní:",
- "clearyourcache": "'''Poznámka:''' Aby sa zmeny prejavili, po uložení musíte vymazať vyrovnávaciu pamäť vášho prehliadača.\n* '''Mozilla Firefox / Safari:''' Držte stlačený ''Shift'' a kliknite na ''Reload'' alebo stlačte buď ''Ctrl-F5'' alebo ''Ctrl-R'' (''⌘-R'' na Mac)\n* '''Google Chrome:''' Stlačte ''Ctrl-Shift-R'' (''⌘-Shift-R'' na Mac)\n* '''Internet Explorer:''' Držte ''Ctrl'' a kliknite na ''Refresh'' alebo stlačte ''Ctrl-F5''\n* '''Opera:''' Vymazať vyrovnávaciu pamäť prehliadača v ponuke ''Tools→Preferences''",
+ "clearyourcache": "<strong>Poznámka:</strong> Aby sa zmeny prejavili, po uložení musíte vymazať vyrovnávaciu pamäť vášho prehliadača.\n* <strong>Mozilla Firefox / Safari:</strong> Držte stlačený <em>Shift</em> a kliknite na ''Reload'' alebo stlačte buď <em>Ctrl-F5</em> alebo <em>Ctrl-R</em> (<em>⌘-R</em> na Mac)\n* <strong>Google Chrome:</strong> Stlačte <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> na Mac)\n* <strong>Internet Explorer:</strong> Držte <em>Ctrl</em> a kliknite na <em>Refresh</em> alebo stlačte <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Prejdite do <em>Menu → Settings</em> (<em>Opera → Preferences</em> on a Mac) a potom do <em>Privacy & security → Clear browsing data → Cached images and files</em>.",
"usercssyoucanpreview": "'''Tip:''' Váš nový CSS pred uložením otestujete stlačením tlačidla „{{int:showpreview}}“.",
"userjsyoucanpreview": "'''Tip:''' Váš nový JS pred uložením otestujete stlačením tlačidla „{{int:showpreview}}“.",
"usercsspreview": "'''Nezabudnite, že toto je iba náhľad vášho používateľského CSS, ešte nebolo uložené!'''",
"previewnote": "'''Nezabudnite, toto je iba náhľad stránky, ktorú upravujete.\nZmeny ešte nie sú uložené!'''",
"continue-editing": "Pokračovať v úpravách",
"previewconflict": "Tento náhľad upravenej stránky zobrazuje text z horného poľa s textom tak, ako sa zobrazí potom, keď ju uložíte.",
- "session_fail_preview": "'''Prepáčte, nemohli sme spracovať váš príspevok kvôli strate údajov relácie.\nSkúste to prosím ešte raz.\nAk to nebude fungovať, skúste sa [[Special:UserLogout|odhlásiť]] a znovu prihlásiť.'''",
- "session_fail_preview_html": "'''Prepáčte! Nemohli sme spracovať vašu úpravu kvôli strate údajov relácie.'''\n\n''Pretože {{SITENAME}} má použitie HTML umožnené, náhľad sa nezobrazí (prevencia pred JavaScript útokmi).''\n\n'''Ak je toto legitímny pokus o úpravu, skúste to prosím znova. Ak to stále nefunguje, skúste sa [[Special:UserLogout|odhlásiť]] a znovu prihlásiť.'''",
+ "session_fail_preview": "Prepáčte, nemohli sme spracovať váš príspevok kvôli strate údajov relácie.\n\n<strong>Prosím, uistite sa, že ste prihlásený a skúste to prosím znova.</strong>.\nAk to nebude fungovať, skúste sa [[Special:UserLogout|odhlásiť]] a znovu prihlásiť. Skontrolujte, či váš prehliadač prijíma cookies z tejto webstránky.",
+ "session_fail_preview_html": "Prepáčte! Nemohli sme spracovať vašu úpravu kvôli strate údajov relácie.\n\n<em>Pretože {{SITENAME}} má použitie HTML umožnené, náhľad sa nezobrazí (prevencia pred JavaScript útokmi).</em>\n\n<strong>Ak je toto legitímny pokus o úpravu, skúste to prosím znova.</strong>.\nAk to nebude fungovať, skúste sa [[Special:UserLogout|odhlásiť]] a znovu prihlásiť. Skontrolujte, či váš prehliadač prijíma cookies z tejto webstránky.",
"token_suffix_mismatch": "'''Vaša úprava bola zamietnutá, pretože váš klient pokazil znaky s diakritikou v editačnom symbole (token). Úprava bola zamietnutá, aby sa zabránilo poškodeniu textu stránky. Toto sa občas stáva, keď používate chybnú anonymnú proxy službu cez webové rozhranie.'''",
"edit_form_incomplete": "'''Niektoré časti editačného formulára nedosiahli server. Prosím, znova skontrolujte, že vaše úpravy sú nepoškodené a skúste to znova.'''",
"editing": "Úprava stránky $1",
"explainconflict": "Niekto iný zmenil túto stránku, zatiaľ čo ste ju upravovali vy.\nHorné okno na úpravy obsahuje text stránky tak, ako je momentálne platný.\nVaše úpravy sú uvedené v dolnom okne na úpravy.\nBudete musieť zlúčiť vaše zmeny s existujúcim textom.\n'''Iba''' obsah horného okna sa uloží, keď stlačíte „{{int:savearticle}}“.",
"yourtext": "Váš text",
"storedversion": "Uložená verzia",
- "nonunicodebrowser": "'''UPOZORNENIE: Váš prehliadač nepodporuje unicode. Dočasným riešením ako bezpečne upravovať stránky je, že ne-ASCII znaky sa v upravovacom textovom poli zobrazia ako zodpovedajúce hexadecimálne hodnoty.'''",
- "editingold": "'''UPOZORNENIE: Upravujete starú\nverziu tejto stránky. Ak vašu úpravu uložíte, prepíšete tým všetky úpravy, ktoré nasledovali po tejto starej verzii.'''",
+ "nonunicodebrowser": "<strong>UPOZORNENIE: Váš prehliadač nepodporuje Unicode.</strong>\nDočasným riešením ako bezpečne upravovať stránky je, že ne-ASCII znaky sa v upravovacom textovom poli zobrazia ako zodpovedajúce hexadecimálne hodnoty.",
+ "editingold": "<strong>UPOZORNENIE: Upravujete starú verziu tejto stránky.</strong>\nAk vašu úpravu uložíte, prepíšete tým všetky úpravy, ktoré nasledovali po tejto starej verzii.",
"yourdiff": "Rozdiely",
- "copyrightwarning": "Nezabudnite, že všetky príspevky do {{GRAMMAR:genitív|{{SITENAME}}}} sa považujú za príspevky pod licenciou $2 (podrobnosti pozri pod $1). Ak nechcete, aby bolo to, čo ste napísali, neúprosne upravované a ďalej ľubovoľne rozširované, tak sem váš text neumiestňujte.<br />\n\nTýmto sa právne zaväzujete, že ste tento text buď napísali sám, alebo že je skopírovaný\nz voľného diela (public domain) alebo podobného zdroja neobmedzeného autorskými právami.\n'''NEUMIESTŇUJTE TU BEZ POVOLENIA DIELA CHRÁNENÉ AUTORSKÝM PRÁVOM!'''",
- "copyrightwarning2": "Prosím uvedomte si, že všetky príspevky do {{GRAMMAR:genitív|{{SITENAME}}}} môžu byť upravované, skracované alebo odstránené inými prispievateľmi. Ak nechcete, aby Vaše texty boli menené, tak ich tu neuverejňujte.<br />\n\nTýmto sa právne zaväzujete, že ste tento text buď napísali sám, alebo že je skopírovaný\nz voľného diela (public domain) alebo podobného zdroja neobmedzeného autorskými právami (podrobnosti: $1).\n'''NEUMIESTŇUJTE SEM BEZ POVOLENIA DIELA CHRÁNENÉ AUTORSKÝM PRÁVOM!'''",
+ "copyrightwarning": "Nezabudnite, že všetky príspevky do {{GRAMMAR:genitív|{{SITENAME}}}} sa považujú za príspevky pod licenciou $2 (podrobnosti pozri pod $1). Ak nechcete, aby bolo to, čo ste napísali, neúprosne upravované a ďalej ľubovoľne rozširované, tak sem váš text neumiestňujte.<br />\n\nTýmto sa právne zaväzujete, že ste tento text buď napísali sám, alebo že je skopírovaný\nz voľného diela (public domain) alebo podobného zdroja neobmedzeného autorskými právami.\n<strong>NEUMIESTŇUJTE SEM BEZ POVOLENIA DIELA CHRÁNENÉ AUTORSKÝM PRÁVOM!</strong>",
+ "copyrightwarning2": "Prosím uvedomte si, že všetky príspevky do {{GRAMMAR:genitív|{{SITENAME}}}} môžu byť upravované, skracované alebo odstránené inými prispievateľmi. Ak nechcete, aby Vaše texty boli menené, tak ich tu neuverejňujte.<br />\n\nTýmto sa právne zaväzujete, že ste tento text buď napísali sám, alebo že je skopírovaný\nz voľného diela (public domain) alebo podobného zdroja neobmedzeného autorskými právami (podrobnosti: $1).\n<strong>NEUMIESTŇUJTE SEM BEZ POVOLENIA DIELA CHRÁNENÉ AUTORSKÝM PRÁVOM!</strong>",
"longpageerror": "'''Chyba: Text, ktorý ste poslali má {{PLURAL:$1|jeden kilobajt|$1 kilobajty|$1 kilobajtov}}, čo je viac ako maximum {{PLURAL:$2|jeden kilobajt|$2 kilobajty|$2 kilobajtov}}.'''",
- "readonlywarning": "'''UPOZORNENIE: Databáza bola počas upravovania stránky zamknutá z dôvodu údržby,\ntakže svoje úpravy momentálne nemôžete uložiť.'''\nMôžete skopírovať a vložiť text do textového súboru a uložiť si ho na neskôr.\n\nSprávca, ktorý ju zamkol, uviedol nasledovné vysvetlenie: $1",
+ "readonlywarning": "<strong>UPOZORNENIE: Databáza bola počas upravovania stránky zamknutá z dôvodu údržby,\ntakže svoje úpravy momentálne nemôžete uložiť.</strong>\nMôžete skopírovať a vložiť text do textového súboru a uložiť si ho na neskôr.\n\nSprávca systému, ktorý ju zamkol, uviedol nasledovné vysvetlenie: $1",
"protectedpagewarning": "'''Upozornenie: Táto stránka bola zamknutá, takže ju môžu upravovať iba používatelia s oprávnením správcu.''' Dolu je pre informáciu posledná položka zo záznamu:",
"semiprotectedpagewarning": "'''Poznámka:''' Táto stránka bola zamknutá tak, aby ju mohli upravovať iba registrovaní používatelia. Dolu je pre informáciu posledná položka zo záznamu:",
"cascadeprotectedwarning": "'''Upozornenie:''' Táto stránka bola zamknutá (takže ju môžu upravovať iba používatelia s privilégiami správcu), pretože je použitá na {{PLURAL:$1|nasledovnej stránke|nasledovných stránkach}} s kaskádovým zamknutím:",
"invalid-content-data": "Neplatné dáta obsahu",
"content-not-allowed-here": "Obsah „$1“ nie je povolený na stránke [[$2]]",
"editwarning-warning": "Ak opustíte túto stránku, môžete tým stratiť všetky vykonané zmeny.\nAk ste prihlásený, toto upozornenie môžete vypnúť v sekcii „{{int:prefs-editing}}“ svojich nastavení.",
- "editpage-notsupportedcontentformat-title": "Obsahový formát nieje podporovaný",
+ "editpage-notsupportedcontentformat-title": "Formát obsahu nie je podporovaný",
"content-model-wikitext": "wikitext",
"content-model-text": "čistý text",
"content-model-javascript": "JavaScript",
"parser-unstrip-loop-warning": "Zistené zacyklenie volania rozširovacej značky",
"parser-unstrip-recursion-limit": "Prektočený limit rekurzie volania rozširovacej značky ($1)",
"converter-manual-rule-error": "Bola zistená chyba v pravidle manuálnej konverzie jazyka",
- "undo-success": "Úpravu je možné vrátiť. Prosím skontrolujte tento rozdiel, čím overíte, že táto úprava je tá, ktorú chcete, a následne uložte zmeny, čím ukončíte vrátenie.",
+ "undo-success": "Úpravu je možné vrátiť.\nProsím, skontrolujte tento rozdiel, čím overíte, že táto úprava je tá, ktorú chcete. Následne uložte zmeny, čím ukončíte vrátenie.",
"undo-failure": "Úpravu nie je možné vrátiť kvôli konfliktným medziľahlým úpravám.",
"undo-norev": "Túto úpravu nie je možné vrátiť, pretože neexistuje alebo bola zmazaná.",
"undo-nochange": "Zdá sa, že úprava už bola zrušená.",
"undo-summary": "Revízia $1 používateľa [[Special:Contributions/$2|$2]] ([[User talk:$2|diskusia]]) bola vrátená",
"undo-summary-username-hidden": "Vrátiť revíziu $1, ktorú vykonal skrytý používateľ",
- "cantcreateaccount-text": "Zakladanie nových účtov z tejto IP adresy ('''$1''') bolo zablokované {{GENDER:$3|používateľom|používateľkou}} [[User:$3|$3]].\n\nDôvod, ktorý $3 {{GENDER:$3|uviedol|uviedla}}, je: ''$2''",
- "cantcreateaccount-range-text": "Zakladanie nových účtov z IP adries v rozsahu <strong>$1</strong>, ktorý zahŕňa aj vašu IP adresu (<strong>$4</strong>), bolo zablokované {{GENDER:$3|používateľom|používateľkou}} [[User:$3|$3]].\n\nDôvod, ktorý $3 {{GENDER:$3|uviedol|uviedla}}, je: <em>$2</em>",
+ "cantcreateaccount-text": "Zakladanie nových účtov z tejto IP adresy (<strong>$1</strong>) bolo zablokované {{GENDER:$3|používateľom|používateľkou}} [[User:$3|$3]].\n\nDôvod, ktorý $3 {{GENDER:$3|uviedol|uviedla}}, je: <em>$2</em>",
+ "cantcreateaccount-range-text": "Zakladanie nových účtov z IP adries v rozsahu <strong>$1</strong>, do ktorého spadá aj vaša IP adresu (<strong>$4</strong>), bolo zablokované {{GENDER:$3|používateľom|používateľkou}} [[User:$3|$3]].\n\nDôvod, ktorý $3 {{GENDER:$3|uviedol|uviedla}}, je: <em>$2</em>",
"viewpagelogs": "Zobraziť záznamy pre túto stránku",
"nohistory": "Pre túto stránku neexistuje história.",
"currentrev": "Aktuálna verzia",
"rev-deleted-user-contribs": "[používateľské meno alebo IP adresa odstránená - úprava skrytá pred prispievateľmi]",
"rev-deleted-text-permission": "Táto revízia stránky bola '''zmazaná'''.\nPodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname mazaní].",
"rev-suppressed-text-permission": "Táto revízia stránky bola <strong>potlačená</strong>. Podrobnosti nájdete v [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zázname potlačení].",
- "rev-deleted-text-unhide": "Táto revízia stránky bola '''zmazaná'''.\nPodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname mazaní].\nAko správca máte stále možnosť [$1 zobraziť túto revíziu] ak chcete.",
- "rev-suppressed-text-unhide": "Táto revízia stránky bola '''potlačená'''.\nPodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zázname potlačení].\nAko správca máte stále možnosť [$1 zobraziť túto revíziu] ak chcete.",
+ "rev-deleted-text-unhide": "Táto revízia stránky bola <strong>zmazaná</strong>.\nPodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname mazaní].\nAko správca máte stále možnosť [$1 zobraziť túto revíziu] ak chcete.",
+ "rev-suppressed-text-unhide": "Táto revízia stránky bola <strong>potlačená</strong>.\nPodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zázname potlačení].\nAko správca máte stále možnosť [$1 zobraziť túto revíziu] ak chcete.",
"rev-deleted-text-view": "Táto revízia stránky bola '''zmazaná'''.\nAko správca {{GRAMMAR:genitív|{{SITENAME}}}} si ju môžete prezrieť;\npodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname mazaní].",
- "rev-suppressed-text-view": "Táto revízia stránky bola '''potlačená'''.\nPodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zázname potlačení].",
+ "rev-suppressed-text-view": "Táto revízia stránky bola <strong>potlačená</strong>.\nPodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zázname potlačení].",
"rev-deleted-no-diff": "Tento rozdiel nemôžete zobraziť, pretože bol '''zmazaný'''.\nPodrobnosti môžete nájsť v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname mazaní].",
"rev-suppressed-no-diff": "Nemôžete zobraziť tento rozdiel, pretože jedna z revízií bola '''zmazaná'''.",
- "rev-deleted-unhide-diff": "Jedna z revízií tohto rozdielu bola '''zmazaná'''.\nPodrobnosti môžete nájsť v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname mazaní].\nAko správca {{GRAMMAR:genitív|{{SITENAME}}}} si [$1 tento rozdiel môžete prezrieť].",
- "rev-suppressed-unhide-diff": "Jedna z revízií tohto rozdielu bola '''potlačená'''.\nPodrobnosti môžete nájsť v [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zázname potlačení].\nAko správca {{GRAMMAR:genitív|{{SITENAME}}}} si [$1 tento rozdiel môžete prezrieť].",
- "rev-deleted-diff-view": "Jedna z revízií tohto rozdielu bola '''zmazaná'''.\nAko správca si môžete tento rozdiel zobraziť; podrobnosti môžete nájsť v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname mazaní].",
- "rev-suppressed-diff-view": "Jedna z revízií tohto rozdielu bola '''potlačená'''.\nAko správca si môžete tento rozdiel zobraziť; podrobnosti môžete nájsť v [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zázname potlačení].",
+ "rev-deleted-unhide-diff": "Jedna z revízií tohto rozdielu bola <strong>zmazaná</strong>.\nPodrobnosti môžete nájsť v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname mazaní].\nAko správca {{GRAMMAR:genitív|{{SITENAME}}}} si [$1 tento rozdiel môžete prezrieť].",
+ "rev-suppressed-unhide-diff": "Jedna z revízií tohto rozdielu bola <strong>potlačená</strong>.\nPodrobnosti môžete nájsť v [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zázname potlačení].\nAko správca {{GRAMMAR:genitív|{{SITENAME}}}} si [$1 tento rozdiel môžete prezrieť].",
+ "rev-deleted-diff-view": "Jedna z revízií tohto rozdielu bola <strong>zmazaná</strong>.\nAko správca si môžete tento rozdiel zobraziť; podrobnosti môžete nájsť v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname mazaní].",
+ "rev-suppressed-diff-view": "Jedna z revízií tohto rozdielu bola <strong>potlačená</strong>.\nAko správca si môžete tento rozdiel zobraziť; podrobnosti môžete nájsť v [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} zázname potlačení].",
"rev-delundel": "zobraziť/skryť",
"rev-showdeleted": "zobraziť",
"revisiondelete": "Zmazať/obnoviť revízie",
"revdelete-legend": "Nastaviť obmedzenia viditeľnosti",
"revdelete-hide-text": "Text revízie",
"revdelete-hide-image": "Skryť obsah súboru",
- "revdelete-hide-name": "Skryť činnosť a cieľ",
+ "revdelete-hide-name": "Skryť cieľ a parametre",
"revdelete-hide-comment": "Zhrnutie úprav",
"revdelete-hide-user": "Používateľské meno/IP redaktora",
"revdelete-hide-restricted": "Zatajiť údaje pred všetkými, aj pred správcami",
"revdelete-submit": "Použiť na {{PLURAL:$1|zvolenú revíziu|zvolené revízie}}",
"revdelete-success": "'''Viditeľnosť revízie bola úspešne aktualizovaná.'''",
"revdelete-failure": "'''Viditeľnosť revízie nebolo možné aktualizovať:'''\n$1",
- "logdelete-success": "'''Viditeľnosť záznamu bola úspešne nastavená.'''",
+ "logdelete-success": "Viditeľnosť záznamu bola úspešne nastavená.",
"logdelete-failure": "'''Viditeľnosť záznamu nebolo možné nastaviť:'''\n$1",
"revdel-restore": "Zmeniť viditeľnosť",
"pagehist": "História stránky",
"showhideselectedversions": "Zobraziť/skryť vybrané revízie",
"editundo": "vrátiť",
"diff-empty": "(Žiaden rozdiel)",
- "diff-multi-sameuser": "({{PLURAL:$1|Jedna medziľahlá úprava|$1 medziľahlé úpravy|$1 medziľahlých úprav}} od rovnakého používateľa.)",
+ "diff-multi-sameuser": "({{PLURAL:$1|Jedna medziľahlá úprava|$1 medziľahlé úpravy|$1 medziľahlých úprav}} od rovnakého používateľa nie {{PLURAL:$1|je zobrazená|sú zobrazené|je zobrazených}}.)",
"diff-multi-otherusers": "({{PLURAL:$1|Jedna medziľahlá úprava|$1 medziľahlé úpravy|$1 medziľahlých úprav}} od {{PLURAL:$2|jedného ďalšieho používateľa|$2 ďalších používateľov}} {{PLURAL:$1|nie je zobrazená|nie sú zobrazené|nie je zobrazených}})",
"diff-multi-manyusers": "({{PLURAL:$1|$1 medziľahlá revízia|$1 medziľahlé revízie|$1 medziľahlých revízií}} od viac ako {{PLURAL:$2|$2 používateľa|$2 používateľov}} {{PLURAL:$1|nie je zobrazená|nie sú zobrazené|nie je zobrazených}})",
"difference-missing-revision": "{{PLURAL:$2|$2 revízia|$2 revízie|$2 revízií}} pre požadovaný rozdiel ($1) {{PLURAL:$2|neexistuje|neexistujú|neexistuje}}.\n\nPravdepodobne ste nasledovali zastaraný odkaz na rozdiel revízií, z ktorých niektorá bola medzičasom odstránená.\nPodrobnosti nájdete v [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} zázname zmazaní].",
"search-suggest": "Mali ste na mysli „$1“?",
"search-rewritten": "Zobrazujú sa výsledky pre $1. Vyhľadať namiesto toho $2.",
"search-interwiki-caption": "Sesterské projekty",
- "search-interwiki-default": "$1 výsledkov:",
+ "search-interwiki-default": "Výsledky z $1:",
"search-interwiki-more": "(viac)",
"search-relatedarticle": "Súvisiace",
"searchrelated": "súvisiace",
"rows": "Riadky:",
"columns": "Stĺpce:",
"searchresultshead": "Vyhľadávanie",
- "stub-threshold": "Prah formátovania odkazu ako výhonok:",
+ "stub-threshold": "Prah formátovania odkazu ako výhonok ($1):",
"stub-threshold-sample-link": "príklad",
"stub-threshold-disabled": "Vypnuté",
"recentchangesdays": "Koľko dní zobrazovať v posledných úpravách:",
"badsig": "Neplatný podpis v pôvodnom tvare; skontrolujte HTML značky.",
"badsiglength": "Váš podpis je príliš dlhý.\nMusí obsahovať menej ako $1 {{PLURAL:$1|znak|znaky|znakov}}.",
"yourgender": "Ako si želáte byť označovaný?",
- "gender-unknown": "Radšej nechcem uviesť",
+ "gender-unknown": "Softvér bude používať rodovo neutrálne slová, vždy keď je to možné, keď vás spomína",
"gender-male": "On upravuje wiki stránky",
"gender-female": "Ona upravuje wiki stránky",
"prefs-help-gender": "Nastavenie tejto voľby nie je povinné.\nSoftvér používa toto nastavenie na správne oslovenie a označenie vás ostatným v závislosti od gramatického rodu. Táto informácia bude verejná.",
"prefs-dateformat": "Formát dátumu",
"prefs-timeoffset": "Časový posun",
"prefs-advancedediting": "Všeobecné možnosti",
- "prefs-editor": "Redaktor",
+ "prefs-editor": "Používateľ",
"prefs-preview": "Náhľad",
"prefs-advancedrc": "Rozšírené možnosti",
"prefs-advancedrendering": "Rozšírené možnosti",
"editusergroup": "Upraviť skupiny {{GENDER:$1|používateľa|používateľky}}",
"editinguser": "Zmena práv používateľa '''[[User:$1|$1]]''' $2",
"userrights-editusergroup": "Upraviť skupiny používateľa",
- "saveusergroups": "Uložiť skupiny používateľa",
+ "saveusergroups": "Uložiť skupiny {{GENDER:$1|používateľa|používateľky}}",
"userrights-groupsmember": "{{GENDER:$2|Člen|Členka}} {{PLURAL:$1|skupiny|skupín}}:",
"userrights-groupsmember-auto": "Implicitne {{GENDER:$2|člen|členka}} {{PLURAL:$1|skupiny|skupín}}:",
"userrights-groups-help": "Môžete zmeniť skupiny, do ktorých je {{GENDER:$1|používateľ zaradený|používateľka zaradená}}.\n* Zaškrtnuté pole znamená, že {{GENDER:$1|používateľ|používateľka}} je v skupine.\n* Nezaškrtnuté pole znamená, že {{GENDER:$1|používateľ|používateľka}} nie je v skupine.\n* Hviezdička (*) znamená, že nemôžete odstrániť skupinu, keď ste ju už pridali resp. naopak.",
"right-move": "Presúvať stránky",
"right-move-subpages": "Presunúť stránky aj s podstránkami",
"right-move-rootuserpages": "Presunúť koreňové stránky používateľa",
- "right-move-categorypages": "Premiestňovanie stránok kategórií",
+ "right-move-categorypages": "Premiestňovať stránky kategórií",
"right-movefile": "Presunúť súbory",
"right-suppressredirect": "Nevytvoriť presmerovanie zo starého názvu pri presúvaní stránky",
"right-upload": "Nahrávať súbory",
"right-writeapi": "Použitie API na zápis",
"right-delete": "Mazať stránky",
"right-bigdelete": "Mazať stránky s veľkou históriou",
- "right-deletelogentry": "Odstrániť a obnoviť špecifické položky",
+ "right-deletelogentry": "Odstrániť a obnoviť špecifické položky záznamu",
"right-deleterevision": "Mazať a obnovovať konkrétne revízie stránok",
"right-deletedhistory": "Zobrazovať zmazané položky histórie bez ich plného textu",
"right-deletedtext": "Zobrazovať zmazané texty a zmeny medzi zmazanými verziami",
"action-read": "čítať túto stránku",
"action-edit": "upravovať túto stránku",
"action-createpage": "vytvárať stránky",
- "action-createtalk": "vytvárať diskusné stránky",
+ "action-createtalk": "vytvoriť túto diskusnú stránku",
"action-createaccount": "vytvoriť tento používateľský účet",
"action-history": "zobraziť históriu tejto stránky",
"action-minoredit": "označiť túto úpravu ako drobnú",
"action-viewmyprivateinfo": "zobraziť vaše súkromné údaje",
"action-editmyprivateinfo": "upraviť vaše súkromné údaje",
"action-editcontentmodel": "upraviť model obsahu stránky",
- "action-managechangetags": "vytvorte a odstráňte značky z databázy",
+ "action-managechangetags": "vytvoriť a (de)aktivovať značky",
"nchanges": "$1 {{PLURAL:$1|úprava|úpravy|úprav}}",
"enhancedrc-since-last-visit": "$1 {{PLURAL:$1|od poslednej návštevy}}",
"enhancedrc-history": "história",
"boteditletter": "b",
"number_of_watching_users_pageview": "[$1 {{PLURAL:$1|sledujúci používateľ|sledujúci používatelia|sledujúcich používateľov}}]",
"rc_categories": "Obmedziť na kategórie (oddeľte znakom „|“)",
- "rc_categories_any": "akékoľvek",
+ "rc_categories_any": "Akékoľvek z vybraných",
"rc-change-size-new": "$1 {{PLURAL:$1|bajt|bajty|bajtov}} po zmene",
"newsectionsummary": "/* $1 */ nová sekcia",
"rc-enhanced-expand": "Zobraziť podrobnosti",
"protectedpages-reason": "Dôvod",
"protectedpages-submit": "Zobraziť stránky",
"protectedpages-unknown-timestamp": "Neznáme",
- "protectedpages-unknown-performer": "Neznámy redaktor",
+ "protectedpages-unknown-performer": "Neznámy používateľ",
"protectedtitles": "Zamknuté názvy",
"protectedtitles-summary": "Táto stránka obsahuje zoznam názvov, ktoré sú momentálne zamknuté proti vytvoreniu. Zoznam existujúcich zamknutých stránok nájdete na stránke [[{{#special:ProtectedPages}}|{{int:protectedpages}}]].",
"protectedtitlesempty": "Tieto parametre momentálne nezamykajú žiadne názvy stránok.",
"createacct-yourpasswordagain-ph": "Ponovno vnesite geslo",
"userlogin-remembermypassword": "Zapomni si me",
"userlogin-signwithsecure": "Uporabi varno povezavo",
+ "cannotlogin-title": "Ne moremo vas prijaviti",
+ "cannotlogin-text": "Prijava ni mogoča.",
"cannotloginnow-title": "Trenutno se ne morete prijaviti",
"cannotloginnow-text": "Prijava ni možna pri uporabi $1.",
+ "cannotcreateaccount-title": "Ne moremo ustvariti računov",
+ "cannotcreateaccount-text": "Neposredno ustvarjanje računov na tem wikiju ni omogočeno.",
"yourdomainname": "Domena",
"password-change-forbidden": "Na tem wikiju ne morete spreminjati gesel.",
"externaldberror": "Pri potrjevanju istovetnosti je prišlo do notranje napake ali pa za osveževanje zunanjega računa nimate dovoljenja.",
"botpasswords-updated-body": "Posodobili smo geslo bota »$1« uporabnika »$2«.",
"botpasswords-deleted-title": "Izbrisali smo geslo bota",
"botpasswords-deleted-body": "Izbrisali smo geslo bota »$1« uporabnika »$2«.",
- "botpasswords-newpassword": "Novo geslo za prijavo z imenom <strong>$1</strong> je <strong>$2</strong>. <em>Prosimo, zabeležite si to za uporabo v prihodnje.</em>",
+ "botpasswords-newpassword": "Novo geslo za prijavo z imenom <strong>$1</strong> je <strong>$2</strong>. <em>Prosimo, zabeležite si to za uporabo v prihodnje.</em> <br> (Za starejše bote, pri katerih mora biti prijavno ime enako uporabniškemu imenu, lahko uporabite <strong>$3</strong> kot uporabniško ime in <strong>$4</strong> kot geslo.)",
"botpasswords-no-provider": "BotPasswordsSessionProvider ni na voljo.",
"botpasswords-restriction-failed": "Omejitve gesla bota preprečujejo to prijavo.",
"botpasswords-invalid-name": "Navedeno uporabniško ime ne vsebuje ločila za geslo bota (»$1«).",
"invalid-content-data": "Neveljavni podatki vsebine",
"content-not-allowed-here": "Vsebina »$1« ni dovoljena na strani [[$2]]",
"editwarning-warning": "Če zapustite stran, boste morda izgubili vse spremembe, ki ste jih naredili.\nČe ste prijavljeni, lahko to opozorilo onemogočite v razdelku »{{int:prefs-editing}}« v svojih nastavitvah.",
+ "editpage-invalidcontentmodel-title": "Model vsebine ni podprt",
+ "editpage-invalidcontentmodel-text": "Model vsebine »$1« ni podprt.",
"editpage-notsupportedcontentformat-title": "Oblika vsebine ni podprta",
"editpage-notsupportedcontentformat-text": "Model vsebine $2 ne podpira oblike vsebine $1.",
"content-model-wikitext": "wikibesedilo",
"pageinfo-article-id": "ID strani",
"pageinfo-language": "Jezik vsebine strani",
"pageinfo-content-model": "Model vsebine strani",
+ "pageinfo-content-model-change": "spremeni",
"pageinfo-robot-policy": "Robotsko indeksiranje",
"pageinfo-robot-index": "Dovoljeno",
"pageinfo-robot-noindex": "Nedovoljeno",
"tag-filter": "Filter [[Special:Tags|oznak]]:",
"tag-filter-submit": "Filtriraj",
"tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Oznaka|Oznaki|Oznake}}]]: $2)",
+ "tag-mw-contentmodelchange": "sprememba modela vsebine",
+ "tag-mw-contentmodelchange-description": "Urejanja, ki [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel spremenijo model vsebine] strani",
"tags-title": "Etikete",
"tags-intro": "Ta stran navaja etikete, s katerimi lahko programje označi urejanja, in njihov pomen.",
"tags-tag": "Ime oznake",
"tags-actions-header": "Dejanja",
"tags-active-yes": "Da",
"tags-active-no": "Ne",
- "tags-source-extension": "Opredeljuje jo razširitev",
+ "tags-source-extension": "Opredeljuje jo programje",
"tags-source-manual": "Ročno jo uporabljajo uporabniki in boti",
"tags-source-none": "Se ne uporablja več",
"tags-edit": "uredi",
"createacct-yourpasswordagain-ph": "Ange lösenordet igen",
"userlogin-remembermypassword": "Håll mig inloggad",
"userlogin-signwithsecure": "Använd säker anslutning",
+ "cannotlogin-title": "Kan inte logga in",
+ "cannotlogin-text": "Det går inte att logga in.",
"cannotloginnow-title": "Kan inte logga in nu",
"cannotloginnow-text": "Det går inte att logga in med $1.",
+ "cannotcreateaccount-title": "Kan inte skapa konton",
+ "cannotcreateaccount-text": "Direkt kontoregistrering är inte aktiverat på denna wiki.",
"yourdomainname": "Din domän",
"password-change-forbidden": "Du kan inte ändra lösenord på denna wiki.",
"externaldberror": "Antingen inträffade autentiseringsproblem med en extern databas, eller så får du inte uppdatera ditt externa konto.",
"pageinfo-article-id": "Sid-ID",
"pageinfo-language": "Språk för sidinnehåll",
"pageinfo-content-model": "Sidinnehållsmodell",
+ "pageinfo-content-model-change": "ändra",
"pageinfo-robot-policy": "Indexering av robotar",
"pageinfo-robot-index": "Tillåten",
"pageinfo-robot-noindex": "Inte tillåten",
"தமிழ்க்குரிசில்",
"Nemo bis",
"JAaron95",
- "Info-farmer"
+ "Info-farmer",
+ "Rakeshonwiki"
]
},
"tog-underline": "அடிக்கோடிட்டத்தை இணை:",
"yourpasswordagain": "கடவுச்சொல்லைத் திரும்ப தட்டச்சிடுக:",
"createacct-yourpasswordagain": "கடவுச்சொல்லை உறுதிசெய்க",
"createacct-yourpasswordagain-ph": "கடவுச்சொல்லை மீளவும் இடுக",
- "remembermypassword": "எனது கடவுச்சொல்லை (கூடியது $1 {{PLURAL:$1|நாள்|நாட்கள்}}) அமர்வுகளிடையே நினைவில் வைத்திருக்கவும்.",
"userlogin-remembermypassword": "இடுபதிந்தே இருக்கவிடவும்",
"userlogin-signwithsecure": "பாதுகாப்பான தொடர்பை உபயோகிக்கவும்",
"cannotloginnow-title": "இப்பொழுது விடுபதிகை செய்ய இயலாது.",
"botpasswords-updated-body": "\"$1\" தானியங்கி கடவுச்சொல் முழுமையாக புதிப்பிக்கப்பட்டது.",
"botpasswords-deleted-title": "தானியங்கி கடவுச்சொல் நீக்கப்பட்டது",
"botpasswords-deleted-body": "\"$1\"-க்கான தானியங்கி கடவுச்சொல் நீக்கப்பட்டது.",
- "botpasswords-newpassword": "<strong>$1</strong>-இற்கு புகுபதிகை செய்வதற்கான புதிய கடவுச்சொல் <strong>$2</strong> ஆகும். <em>தயவு செய்து வருங்கால மேற்கோளுக்கு இதனை பதிக.</em>",
+ "botpasswords-newpassword": "<strong>$1</strong>-இற்கு புகுபதிகை செய்வதற்கான புதிய கடவுச்சொல் <strong>$2</strong> ஆகும். <em>தயவு செய்து வருங்கால மேற்கோளுக்கு இதனை பதிக.</em><br>(பழைய முகவர்கள் பயனாளர் பெயரை உள்ளீட்டிற்கு பயன்படுத்தலாம், மேலும் நீங்கள் <strong>$3</strong> ஐ பயனாளர் பெயராகவும் <strong>$4</strong> ஐ கடவுச்சொல்லாகவும் பயன்படுத்தலாம்.)",
"botpasswords-no-provider": "தானியங்கிகடவுச்சொல்அமர்வுவழங்குநர் பயன்பாட்டில் இல்லை.",
"botpasswords-restriction-failed": "தானியங்கி கடவுச்சொல் புகுபதிகை செய்ய தடுக்கிறது.",
"botpasswords-invalid-name": "தானியங்கி கடவுச்சொல் பிரிப்பானை (\"$1\") குறிக்கப்பட்ட பயனர் பெயர் கொண்டிருக்கவில்லை.",
"minoredit": "இது ஒரு சிறு தொகுப்பு",
"watchthis": "இக்கட்டுரையைக் கவனிக்கவும்",
"savearticle": "பக்கத்தைச் சேமி",
+ "savechanges": "மாற்றங்களைச் சேமி",
"publishpage": "பக்கத்தைப் பதிப்பிடுக",
"publishchanges": "மாற்றங்களைப் பதிப்பிடுக",
"preview": "முன்தோற்றம்",
"invalid-content-data": "செல்லாத உள்ளடக்கத் தரவு",
"content-not-allowed-here": "\"$1\" உள்ளடக்கம் [[$2]] பக்கத்தில் அனுமதிக்கப்படவில்லை.",
"editwarning-warning": "இந்த பக்கத்தை விட்டு செல்வது நீங்கள் ஏற்படுத்திய மாற்றங்களை இழக்க வழிவகுக்கும்.\nநீங்கள் புகுபதிந்திருந்தால், இந்த எச்சரிக்கையை உங்கள் விருப்பத்தேர்வில் உள்ள \"{{int:prefs-editing}}\" பகுதி மூலம் நீக்கலாம்.",
+ "editpage-invalidcontentmodel-title": "உள்ளடக்க அமைப்பை ஆதரிக்க இயலாது",
+ "editpage-invalidcontentmodel-text": "\"$1\" வகை உள்ளுறை ஏற்பதில்லை.",
"editpage-notsupportedcontentformat-title": "உள்ளடக்க அமைப்பு ஆதரவில்லாதது",
"editpage-notsupportedcontentformat-text": "உள்ளடக்க அமைப்பு $1 ஆனது உள்ளடக்க வகை $2 ஆல் ஆதரிக்கப்படாதது.",
"content-model-wikitext": "விக்கிஉரை",
"tag-filter": "[[Special:Tags|குறிச்சொல்]] வடிப்பான்:",
"tag-filter-submit": "வடிகட்டி",
"tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|அடையாளம்|அடையாளங்கள்}}]]: $2)",
+ "tag-mw-contentmodelchange": "உள்ளடக்க மாதிரி மாற்றம்",
+ "tag-mw-contentmodelchange-description": "திருத்து\n[https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel change the content model]",
"tags-title": "குறிச்சொற்கள்",
"tags-intro": "இப்பக்கத்தின் மென்பொருள் ஒரு திருத்ததுடனான குறியீடு என்று குறிச்சொற்கள், மற்றும் அவற்றின் பொருளை பட்டியலிடுகிறது.",
"tags-tag": "குறிச்சொல்",
"tags-actions-header": "செயல்கள்",
"tags-active-yes": "ஆம்",
"tags-active-no": "இல்லை",
- "tags-source-extension": "வà¯\86ளியிணà¯\88பà¯\8dபà¯\81 à®®à¯\82லமà¯\8d வரà¯\88யறà¯\81à®\95à¯\8dà®\95பà¯\8dபà®\9fà¯\8dà®\9fதà¯\81",
+ "tags-source-extension": "à®®à¯\86னà¯\8dபà¯\8aà®°à¯\81ளà¯\8d à®®à¯\82லமà¯\8d வரà¯\88யறà¯\81à®\95à¯\8dà®\95பà¯\8dபà®\9fà¯\8dà®\9fதà¯\81.",
"tags-source-manual": "பயனர் மற்றும் தானியங்கியால் செயல்படுத்தப்படுவது",
"tags-source-none": "பயன்பாட்டில் இல்லை",
"tags-edit": "தொகு",
"Lokesha kunchadka"
]
},
- "tog-underline": "ಲಿà²\82à²\95à³\8dâ\80\99ಲà³\86ದ ತಿರà³\8dತà³\8d à²\97à³\86ರà³\86(à²\85à²\82ಡರà³\8d ಲà³\88ನà³\8d) ಪಾಡà³\8dâ\80\99ಲೆ",
- "tog-hideminor": "à²\8eಲà³\8dಯà³\86ಲà³\8dಯ ಬದಲಾವಣೆಲೆನ್ ದೆಂಗಾಲೆ",
- "tog-hidepatrolled": "à²\95ಾತà³\8aà²\82ದಿಪà³\8dಪà³\81ನ ಸà²\82ಪದನà³\86ಲà³\86ನà³\8d à²\87à²\82à²\9aಿಪà³\8aದ ಬದಲಾವಣà³\86ಡà³\8d à²\85ಡà³\86à²\82à²\97ಲ",
- "tog-newpageshidepatrolled": "à²\95ಾತà³\8aà²\82ದಿಪà³\8dಪà³\81ನ ಪà³\81à²\9fà³\8aಲà³\86ನà³\8d ಪà³\8aಸ ಪà³\81à²\9fà³\8aà²\95à³\81ಲà³\86 ಪà²\9fà³\8dà²\9fಿಡà³\8d à²\85ಡà³\86à²\82à²\97ಲ",
- "tog-hidecategorization": "ವಿà²\82à²\97ಡಿತà³\8dâ\80\8dನ ಪà³\81à²\9fà³\8aಲà³\86ನà³\8d à²\85ಡà³\86à²\82à²\97ಲ",
- "tog-extendwatchlist": "à²\95à³\87ವಲà³\8a à²\87à²\82à²\9aಿಪà³\8aದ ಬದಲಾವಣà³\86ಲತà³\8dತà²\82ದà³\86, ಸà²\82ಬà²\82ದà³\8a à²\87ಪà³\8dಪà³\81ನ ಮಾತ ಬದಲಾವಣೆನ್ಲಾ ತೋಜುನಂಚನೆ ಪಟ್ಟಿನ್ ವಿಸ್ತರಿಸಲೆ",
- "tog-usenewrc": "à²\87à²\82à²\9aಿಪà³\8aದ ಬದಲಾವಣà³\86 ಬà³\8aà²\95à³\8dà²\95à³\8a ವà³\80à²\95à³\8dಷಣಾಪà²\9fà³\8dà²\9fಿಡà³\8d à²\97à³\81à²\82ಪà³\81 ಪà³\81à²\9fà³\8a ಬದಲಾವಣೆ",
- "tog-numberheadings": "ಹà³\86ಡà³\8dಡಿà²\82à²\97à³\8dâ\80\99ಲà³\86à²\97à³\8d ಸà²\82à²\96à³\8dಯà³\86ಲà³\86ನà³\8d ತà³\8aà²\9cà³\8dಪಾಲà³\86",
- "tog-showtoolbar": "ಸà²\82ಪಾದನà³\86ದ à²\89ಪà²\95ರಣೊ ಪಟ್ಟಿನ್ ತೋಜಾವು",
+ "tog-underline": "ಲಿà²\82à²\95à³\8dâ\80\8dಲà³\86ದ ತಿರà³\8dತà³\8d à²\97à³\86ರà³\86(à²\85à²\82ಡರà³\8d ಲà³\88ನà³\8d) ಪಾಡà³\8dâ\80\8dಲೆ",
+ "tog-hideminor": "à²\8eಲà³\8dಯà³\86ಲà³\8dಯ ಬದಲಾವನೆಲೆನ್ ದೆಂಗಾಲೆ",
+ "tog-hidepatrolled": "à²\95ಾತà³\8aà²\82ದಿಪà³\8dಪà³\81ನ ಸà²\82ಪದನà³\86ಲà³\86ನà³\8d à²\87à²\82à²\9aಿಪà³\8aದ ಬದಲಾವನà³\86ಡà³\8d ದà³\86à²\82à²\97ಾಲ",
+ "tog-newpageshidepatrolled": "à²\95ಾತà³\8aà²\82ದಿಪà³\8dಪà³\81ನ ಪà³\81à²\9fà³\8aಲà³\86ನà³\8d ಪà³\8aಸ ಪà³\81à²\9fà³\8aà²\95à³\81ಲà³\86 ಪà²\9fà³\8dà²\9fಿಡà³\8d ದà³\86à²\82à²\97ಾಲ",
+ "tog-hidecategorization": "ವಿà²\82à²\97ಡಿತà³\8dâ\80\8dನ ಪà³\81à²\9fà³\8aಲà³\86ನà³\8d ದà³\86à²\82à²\97ಾಲ",
+ "tog-extendwatchlist": "à²\95à³\87ವಲà³\8a à²\87à²\82à²\9aಿಪà³\8aದ ಬದಲಾವನà³\86ಲತà³\8dತà²\82ದà³\86, ಸà²\82ಬà²\82ದà³\8a à²\87ಪà³\8dಪà³\81ನ ಮಾತ ಬದಲಾವನೆನ್ಲಾ ತೋಜುನಂಚನೆ ಪಟ್ಟಿನ್ ವಿಸ್ತರಿಸಲೆ",
+ "tog-usenewrc": "à²\87à²\82à²\9aಿಪà³\8aದ ಬದಲಾವನà³\86 ಬà³\8aà²\95à³\8dà²\95à³\8a ವà³\80à²\95à³\8dಷಣಾಪà²\9fà³\8dà²\9fಿಡà³\8d à²\97à³\81à²\82ಪà³\81 ಪà³\81à²\9fà³\8a ಬದಲಾವನೆ",
+ "tog-numberheadings": "ತರà³\86ಬರವà³\81ಲà³\86à²\97à³\8d à²\85à²\82à²\95à³\86ಲà³\86ನà³\8d ತà³\8bà²\9cಾವà³\81",
+ "tog-showtoolbar": "ಸà²\82ಪಾದನà³\86ದ à²\89ಪà²\95ರನೊ ಪಟ್ಟಿನ್ ತೋಜಾವು",
"tog-editondblclick": "ರಡ್ಡ್ ಸರ್ತಿ ಒತ್ತ್ನಗ ಪುಟೊನು ಸಂಪೊಲಿಪುನಂಚ ಆವಡ್",
"tog-editsectiononrightclick": "ಪುಟೊತ ವಿಬಾಗೊಲೆನ್ ಐತ ಸೀರ್ಸಿಕೆನ್ ರಡ್ಡ್ ಸರ್ತಿ ಒತ್ತ್ನಗ ಸಂಪೊಲಿಪುನಂಚ ಉಪ್ಪಡ್",
- "tog-watchcreations": "ಯಾನà³\8d ಶುರು ಮಲ್ತಿನ ಲೇಕನೊಲೆನ್ ಎನ್ನ ವೀಕ್ಷಣಾಪಟ್ಟಿಗ್ ಸೇರ್ಪಾಲೆ",
+ "tog-watchcreations": "ಯಾನà³\8d ಸುರು ಮಲ್ತಿನ ಲೇಕನೊಲೆನ್ ಎನ್ನ ವೀಕ್ಷಣಾಪಟ್ಟಿಗ್ ಸೇರ್ಪಾಲೆ",
"tog-watchdefault": "ಯಾನ್ ಸಂಪೊಲಿಪುನ ಪುಟೊಲೆನ್ ವೀಕ್ಷಣಾಪಟ್ಟಿಗ್ ಸೇರ್ಪಾಲೆ",
- "tog-watchmoves": "ಯಾನ್ ಸ್ತಲಾಂತರಿಸುನ ಪುಟೊಲೆನ್ ಎನ್ನ ವೀಕ್ಷಣಾಪಟ್ಟಿಗ್ ಸೇರ್ಪಾಲೆ",
+ "tog-watchmoves": "ಯಾನà³\8d ಸà³\8dತಲಾà²\82ತರಿಸಪà³\81ನ ಪà³\81à²\9fà³\8aಲà³\86ನà³\8d à²\8eನà³\8dನ ವà³\80à²\95à³\8dಷಣಾಪà²\9fà³\8dà²\9fಿà²\97à³\8d ಸà³\87ರà³\8dಪಾಲà³\86",
"tog-watchdeletion": "ಯಾನ್ ದೆತ್ತ್ ಪಾಡುನ ಪುಟೊಲೆನ್ ಎನ್ನ ವೀಕ್ಷಣಾಪಟ್ಟಿಗ್ ಸೇರ್ಪಾಲೆ",
- "tog-watchuploads": "ಎನ್ನ ಅಪ್ಲೋಡ್ ಪಟ್ಟಿಗ್ ಪೊಸ ಕಡತೊಲೆನ್ ಸೇರಲ",
+ "tog-watchuploads": "ಎನ್ನ ಅಪ್ಲೋಡ್ ಪಟ್ಟಿಗ್ ಪೊಸ ಕಡತೊಲೆನ್ ಸೇರಲ",
"tog-watchrollback": "ಯಾನ್ ಪಿರ ದೆತೊನುನ ಪುಟೊಲೆನ್ ಎನ್ನ ಗುಮನೊಗು ಸೇರಲೆ",
- "tog-minordefault": "ಪà³\82ರಾ ಸà²\82ಪಾದನà³\86ನà³\8dಲಾ à²\8eಲà³\8dಯ ಪà²\82ಡà³\8dâ\80\99ದ್ ಗುರ್ತ ಮಲ್ಪುಲೆ",
- "tog-previewontop": "ಮà³\81ನà³\8dನà³\8bà²\9fನà³\8d ಸà²\82ಪಾದನà³\86 à²\85à²\82à²\95ಣದ ಮಿತ್ತ್ ತೊಜ್ಪಾಲೆ",
- "tog-previewonfirst": "ಶà³\81ರà³\81ತ ಬದಲಾವಣೆದ ಬೊಕ್ಕ ಮನ್ನೋಟನ್ ತೊಜ್ಪಾಲೆ",
+ "tog-minordefault": "ಪà³\82ರಾ ಸà²\82ಪಾದನà³\86ನà³\8dಲಾ à²\8eಲà³\8dಯ ಪà²\82ಡà³\8dâ\80\8dದ್ ಗುರ್ತ ಮಲ್ಪುಲೆ",
+ "tog-previewontop": "ಮà³\81ನà³\8dನà³\8bà²\9fನà³\8d ಸà²\82ಪಾದನà³\86 à²\85à²\82à²\95ನà³\8aದ ಮಿತ್ತ್ ತೊಜ್ಪಾಲೆ",
+ "tog-previewonfirst": "ಸà³\81ತ ಬದಲಾವನೆದ ಬೊಕ್ಕ ಮನ್ನೋಟನ್ ತೊಜ್ಪಾಲೆ",
"tog-enotifwatchlistpages": "ಎನ್ನ ವೀಕ್ಷಣಾಪಟ್ಟಿಡ್ ಉಪ್ಪುನಂಚಿನ ಒವಾಂಡಲ ಪುಟೊ ಬದಲಾನಗ ಎಂಕ್ ಇ-ಅಂಚೆ ಕಡಪುಡ್ಲೆ",
"tog-enotifusertalkpages": "ಎನ್ನ ಚರ್ಚೆ ಪುಟ ಬದಲಾಂಡ ಎಂಕ್ ಇ-ಮೇಲ್ ಕಡಪುಡ್ಲೆ",
- "tog-enotifminoredits": "à²\8eಲà³\8dಯà³\86ಲà³\8dಯ ಬದಲಾವಣೆ ಆಂಡಲ ಎಂಕ್ ಇ-ಅಂಚೆ ಕಡಪುಡ್ಲೆ",
- "tog-enotifrevealaddr": "ಪà³\8dರà²\95à²\9fಣà³\86 à²\87-ಮà³\87ಲà³\8dâ\80\99ಡ್ ಎನ್ನ ಇ-ಮೇಲ್ ವಿಳಾಸನ್ ತೊಜ್ಪಾಲೆ",
- "tog-shownumberswatching": "ಪà³\81à²\9fà³\8aನà³\81 ತà³\82ವà³\8aà²\82ದà³\81ಪà³\8dಪà³\81ನà²\82à²\9aಿನ ಸದಸà³\8dಯà³\86ರà³\8dâ\80\99ನ ಸಂಖ್ಯೆನ್ ತೊಜ್ಪಾಲೆ",
+ "tog-enotifminoredits": "à²\8eಲà³\8dಯà³\86ಲà³\8dಯ ಬದಲಾವನೆ ಆಂಡಲ ಎಂಕ್ ಇ-ಅಂಚೆ ಕಡಪುಡ್ಲೆ",
+ "tog-enotifrevealaddr": "ಪà³\8dರà²\95à²\9fಣà³\86 à²\87-ಮà³\87ಲà³\8dâ\80\8dಡ್ ಎನ್ನ ಇ-ಮೇಲ್ ವಿಳಾಸನ್ ತೊಜ್ಪಾಲೆ",
+ "tog-shownumberswatching": "ಪà³\81à²\9fà³\8aನà³\81 ತà³\82ವà³\8aà²\82ದà³\81ಪà³\8dಪà³\81ನà²\82à²\9aಿನ ಸದಸà³\8dಯà³\86ರà³\8dâ\80\8dನ ಸಂಖ್ಯೆನ್ ತೊಜ್ಪಾಲೆ",
"tog-oldsig": "ಇತ್ತೆದ ಸಹಿ",
"tog-fancysig": "ವಿಕಿಟೆಕ್ಸ್ಟ್ಗ್ ದಸ್ಕತ್ತ್ದ ಉಪಚಾರೊ(ಸ್ವಂತೊ ಚಾಲನೆದ ಕೊಂಡಿ ಇದ್ಯಂದಿಲೆಕ)",
"tog-uselivepreview": "ನೇರೊ ಮುನ್ನೋಟೊನು ಉಪಯೋಗ ಮಲ್ಪುಲೆ",
- "tog-forceeditsummary": "ಸà²\82ಪಾದನà³\86 ಸಾರಾà²\82ಶà³\8aನà³\81 à²\96ಾಲಿ ಬà³\81ಡà³\8dâ\80\99ನà³\8dಡ್ ಎಂಕ್ ನೆನಪು ಮಲ್ಪುಲೆ",
- "tog-watchlisthideown": "ವà³\80à²\95à³\8dಷಣಾಪà²\9fà³\8dà²\9fಿಡà³\8d à²\8eನà³\8dನ ಸà²\82ಪಾದನà³\86ಲà³\86ನà³\8d ತà³\8aà²\9cà³\8dâ\80\99ಪಾವà³\8aಚಿ",
+ "tog-forceeditsummary": "ಸà²\82ಪಾದನà³\86 ಸಾರಾà²\82ಸà³\8aನà³\81 à²\95ಾಲಿ ಬà³\81ಡà³\8dâ\80\8dà²\82ದ್ ಎಂಕ್ ನೆನಪು ಮಲ್ಪುಲೆ",
+ "tog-watchlisthideown": "ವà³\80à²\95à³\8dಷಣಾಪà²\9fà³\8dà²\9fಿಡà³\8d à²\8eನà³\8dನ ಸà²\82ಪಾದನà³\86ಲà³\86ನà³\8d ತà³\8aà²\9cà³\8dâ\80\8dಪಾವà³\8aಡà³\8dಚಿ",
"tog-watchlisthidebots": "ವೀಕ್ಷಣಾಪಟ್ಟಿಡ್ ಬಾಟ್ ಸಂಪಾದನೆಲೆನ್ ದೆಂಗಾಲೆ",
"tog-watchlisthideminor": "ಎಲ್ಯ ಬದಲಾವಣೆಲೆನ್ ವೀಕ್ಷಣಾಪಟ್ಟಿರ್ದ್ ದೆಂಗಾಲೆ",
- "tog-watchlisthideliu": "ಲಾà²\97ಿನà³\8d à²\86ತಿನà²\82à²\9aಿನ ಸದಸà³\8dಯà³\86ರà³\8dâ\80\99ನ ಸಂಪಾದನೆಲೆನ್ ವೀಕ್ಷಣಾಪಟ್ಟಿರ್ದ್ ದೆಂಗಾಲೆ",
+ "tog-watchlisthideliu": "ಲಾà²\97ಿನà³\8d à²\86ತಿನà²\82à²\9aಿನ ಸದಸà³\8dಯà³\86ರà³\8dâ\80\8dನ ಸಂಪಾದನೆಲೆನ್ ವೀಕ್ಷಣಾಪಟ್ಟಿರ್ದ್ ದೆಂಗಾಲೆ",
"tog-watchlisthideanons": "ಪುದರಿಜ್ಜಂದಿನ ಬಳಕೆದಾರನ ಸಂಪಾದನೆಲೆನ್ ವೀಕ್ಷಣಾಪಟ್ಟಿರ್ದ್ ದೆಂಗಾಲೆ",
"tog-watchlisthidepatrolled": "ವೀಕ್ಷಣಾಪಟ್ಟಿಡ್ ಬಾಟ್ ಸಂಪಾದನೆಲೆನ್ ದೆಂಗಾಲೆ",
"tog-watchlisthidecategorization": "ವಿಂಗಡಿತ್ನ ಪುಟೊಲೆನ್ ಅಡೆಂಗಲ",
"tooltip-summary": "ಒಂಜಿ ಎಲ್ಯ ಸಾರಾಂಸೊ ಕೊರ್ಲೆ",
"simpleantispam-label": "ಯಾಂಟಿ-ಸ್ಪಾಮ್ ಚೆಕ್.\nಮುಲ್ಪ <strong>ದಿಂಜಾವೊಡ್ಚಿ</strong>",
"pageinfo-article-id": "ಪುಟೊದ ಐಡಿ",
+ "pageinfo-content-model-change": "ಬದಲಾವಣೆಲು",
"pageinfo-toolboxlink": "ಪುಟೊದ ಮಾಹಿತಿ",
"pageinfo-contentpage-yes": "ಅಂದ್",
"pageinfo-protect-cascading-yes": "ಅಂದ್",
"mergehistory-empty": "ఏ కూర్పులనూ విలీనం చెయ్యలేము.",
"mergehistory-done": "$1 యొక్క $3 {{PLURAL:$3|కూర్పుని|కూర్పులను}} [[:$2]] లోనికి జయప్రదంగా విలీనం చేసాం.",
"mergehistory-fail": "చరితాన్ని విలీనం చెయ్యలేకపోయాం. పేజీని, సమయాలను సరిచూసుకోండి.",
+ "mergehistory-fail-bad-timestamp": "కాలముద్ర చెల్లదు.",
"mergehistory-no-source": "మూలం పేజీ, $1 లేదు.",
"mergehistory-no-destination": "గమ్యం పేజీ, $1 లేదు.",
"mergehistory-invalid-source": "మూలం పేజీకి సరైన పేరు ఉండాలి.",
"grant-group-email": "ఈమెయిలు పంపించడం",
"grant-group-administration": "నిర్వాహక చర్యలు చేపట్టడం",
"grant-group-private-information": "మీ గోపనీయ డేటాను చూడడం",
+ "grant-basic": "ప్రాథమిక హక్కులు",
"newuserlogpage": "కొత్త వాడుకరుల చిట్టా",
"newuserlogpagetext": "ఇది వాడుకరి నమోదుల చిట్టా.",
"rightslog": "వాడుకరుల హక్కుల మార్పుల చిట్టా",
"apihelp-no-such-module": "\"$1\" మాడ్యూలు కనబడలేదు.",
"apisandbox": "API ప్రయోగశాల",
"apisandbox-api-disabled": "ఈ సైటులో API అచేతనమై ఉంది.",
+ "apisandbox-unfullscreen": "పేజీను చూపించు",
"apisandbox-submit": "అభ్యర్ధించు",
"apisandbox-reset": "తుడిచివేయి",
- "apisandbox-examples": "ఉదాహరణ",
- "apisandbox-results": "ఫలితం",
+ "apisandbox-retry": "మళ్ళీ ప్రయత్నించు",
+ "apisandbox-helpurls": "సహాయపు లంకెలు",
+ "apisandbox-examples": "ఉదాహరణలు",
+ "apisandbox-dynamic-parameters": "అదనపు పరామితులు",
+ "apisandbox-dynamic-parameters-add-label": "పరామితిని చేర్చు:",
+ "apisandbox-dynamic-parameters-add-placeholder": "పరామితి పేరు",
+ "apisandbox-dynamic-error-exists": "\"$1\" అనే పరామితి ఇప్పటికే ఉంది.",
+ "apisandbox-results": "ఫలితాలు",
"apisandbox-request-url-label": "అభ్యర్థన URL:",
"apisandbox-request-time": "అభ్యర్ధన సమయం: $1",
"booksources": "పుస్తక మూలాలు",
"sessionfailure-title": "సెషను వైఫల్యం",
"sessionfailure": "మీ ప్రవేశపు సెషనుతో ఏదో సమస్య ఉన్నట్లుంది;\nసెషను హైజాకు కాకుండా ఈ చర్యను రద్దు చేసాం.\n\"back\" కొట్టి, ఎక్కడి నుండి వచ్చారో ఆ పేజీని మళ్ళీ లోడు చేసి, తిరిగి ప్రయత్నించండి.",
"changecontentmodel-reason-label": "కారణం:",
+ "changecontentmodel-submit": "మార్చు",
"protectlogpage": "సంరక్షణల చిట్టా",
"protectlogtext": "ఈ క్రింద ఉన్నది పేజీల సంరక్షణలకు జరిగిన మార్పుల జాబితా.\nప్రస్తుతం అమలులో ఉన్న సంరక్షణలకై [[Special:ProtectedPages|సంరక్షిత పేజీల జాబితా]]ను చూడండి.",
"protectedarticle": "\"[[$1]]\" సంరక్షించబడింది.",
"confirm-watch-top": "ఈ పుటను మీ వీక్షణ జాబితాలో చేర్చాలా?",
"confirm-unwatch-button": "సరే",
"confirm-unwatch-top": "ఈ పుటను మీ వీక్షణ జాబితా నుండి తొలగించాలా?",
+ "confirm-rollback-button": "సరే",
"quotation-marks": "“$1”",
"imgmultipageprev": "← మునుపటి పేజీ",
"imgmultipagenext": "తరువాతి పేజీ →",
"tags-edit-title": "ట్యాగులను సవరించు",
"tags-edit-manage-link": "ట్యాగులను నిర్వహించండి",
"tags-edit-existing-tags": "ప్రస్తుత ట్యాగులు:",
- "tags-edit-existing-tags-none": "''ఏమీలేవు''",
+ "tags-edit-existing-tags-none": "<em>ఏమీలేవు</em>",
"tags-edit-new-tags": "కొత్త ట్యాగులు:",
"tags-edit-add": "ఈ ట్యాగులను చేర్చు:",
"tags-edit-remove": "ఈ ట్యాగులను తొలగించు:",
"special-characters-title-emdash": "ఎమ్ డాష్",
"special-characters-title-minus": "మైనస్ గుర్తు",
"mw-widgets-dateinput-no-date": "ఏ తేదీనీ ఎంచుకోలేదు",
- "mw-widgets-titleinput-description-new-page": "పేజీ ఇంకా లేదు"
+ "mw-widgets-titleinput-description-new-page": "పేజీ ఇంకా లేదు",
+ "log-action-filter-all": "అన్నీ",
+ "authmanager-realname-label": "అసలు పేరు",
+ "authmanager-realname-help": "వాడుకరి అసలు పేరు",
+ "authmanager-provider-temporarypassword": "తాత్కాలిక సంకేతపదం",
+ "credentialsform-account": "ఖాతా పేరు:"
}
"tog-hideminor": "حالیہ تبدیلیوں میں معمولی ترامیم چھپائیں",
"tog-hidepatrolled": "حالیہ تبدیلیوں میں گشتی ترامیم چھپائیں",
"tog-newpageshidepatrolled": "جدید صفحات کی فہرست میں مراجعت شدہ صفحات چھپائیں",
- "tog-extendwatchlist": "حالیہ ترین تبدیلیوں کے بجائے جملہ تبدیلیاں دیکھنے کے لیے زیر نظر فہرست کی توسیع کریں",
- "tog-usenewrc": "حالیہ تبدیلیاں اور زیر نظر فہرست میں تبدیلیوں کو بلحاظ صفحہ گروہ بند کیجئے",
+ "tog-hidecategorization": "صفحات کی زمرہ بندی چھپائیں",
+ "tog-extendwatchlist": "حالیہ ترین تبدیلیوں کی بجائے تمام تبدیلیاں دیکھنے کے لیے زیر نظر فہرست کو وسیع کریں",
+ "tog-usenewrc": "حالیہ تبدیلیاں اور زیر نظر فہرست میں تبدیلیوں کو بلحاظ صفحہ گروہ بند کریں",
"tog-numberheadings": "سرخیوں کو خودکار نمبر دیں",
"tog-showtoolbar": "آلات ترمیم دکھائیں",
"tog-editondblclick": "دو کلک پر صفحات کی ترمیم کریں",
"tog-watchdefault": "میرے ترمیم شدہ صفحات اور فائلوں کو میری زیر نظر فہرست میں شامل کریں",
"tog-watchmoves": "میرے منتقل کردہ صفحات اور فائلوں کو میری زیر نظر فہرست میں شامل کریں",
"tog-watchdeletion": "میرے حذف کردہ صفحات اور فائلوں کو میری زیر نظر فہرست میں شامل کریں",
+ "tog-watchuploads": "میری اپلوڈ کردہ نئی فائلوں کو زیر نظر فہرست میں شامل کریں",
"tog-watchrollback": "میرے استرجع کردہ صفحات کو میری زیر نظر فہرست میں شامل کریں",
"tog-minordefault": "ہمیشہ میری تمام ترامیم کو معمولی ترمیم کے طور پر نشان زد کریں",
"tog-previewontop": "خانہ ترمیم سے پہلے نمائش دکھائیں",
"tog-watchlisthidebots": "زیرِنظر فہرست سے روبہ جاتی ترامیم چھپائیں",
"tog-watchlisthideminor": "زیرِنظر فہرست سے معمولی ترامیم چھپائیں",
"tog-watchlisthideliu": "زیرِنظر فہرست سے داخلِ نوشتہ شدہ صارفین کی ترامیم چھپائیں",
+ "tog-watchlistreloadautomatically": "کسی مقطار میں تبدیلی کے بعد زیر نظر فہرست کو خودکار طور پر تازہ کریں (جاوا اسکرپٹ درکار)",
"tog-watchlisthideanons": "زیرِنظر فہرست سے نامعلوم صارفین کی ترامیم چھپائیں",
"tog-watchlisthidepatrolled": "زیرِنظر فہرست سے مراجع شدہ ترامیم چھپائیں",
- "tog-ccmeonemails": "دیگر صارفین کو ارسال کردہ برقی خطوط کی نقول مجھے بھی ارسال کریں۔",
- "tog-diffonly": "فرق کے نیچے صفحے کے مشمولات نہ دکھائیں",
+ "tog-watchlisthidecategorization": "صفحات کی زمرہ بندی چھپائیں",
+ "tog-ccmeonemails": "دیگر صارفین کو ارسال کردہ برقی خطوط کی نقل مجھے بھی ارسال کریں۔",
+ "tog-diffonly": "فرق کے نیچے صفحے کے مندرجات نہ دکھائیں",
"tog-showhiddencats": "پوشیدہ زمرہ جات دکھائیں",
- "tog-norollbackdiff": "استرجع Ú©Û\8c اÙ\86جاÙ\85 دÛ\81Û\8c Ú©Û\92 بعد Ù\81رÙ\82 ترک کریں",
+ "tog-norollbackdiff": "استرجع Ú©Û\92 بعد Ù\81رÙ\82 Ù\86Û\81 دکھائیں",
"tog-useeditwarning": "غیر محفوظ تبدیلیاں چھوڑنے پر مجھے آگاہ کریں",
"tog-prefershttps": "لاگ ان رہنے کے دوران ہمیشہ محفوظ کنیکشن استعمال کریں",
"underline-always": "ہمیشہ",
"underline-never": "کبھی نہیں",
- "underline-default": "جلد یا براؤزر کا ڈیفالٹ",
+ "underline-default": "پوشاک یا براؤزر کا طے شدہ",
"editfont-style": "خانۂ ترمیم کا فانٹ:",
- "editfont-default": "براؤزر کا ڈیفالٹ",
+ "editfont-default": "براؤزر کا طے شدہ",
"editfont-monospace": "مونوسپیسڈ فونٹ",
"editfont-sansserif": "سنس سیرف فونٹ",
"editfont-serif": "سیرف فونٹ",
"nosuchaction": "کوئی سا عمل نہیں",
"nosuchactiontext": "URL کی جانب سے مختص کیا گیا عمل درست نہیں.\nآپ نے شاید URL غلط لکھا، یا کسی غیر صحیح ربط کی پیروی کی ہے.\n{{اِس سے SITENAME کے زیرِ استعمال مصنع لطیف میں کھٹمل کی نشاندہی کا بھی اندیشہ ہے}}.",
"nosuchspecialpage": "کوئی ایسا خاص صفحہ نہیں",
- "nospecialpagetext": "<strong>آپ نے ایک ناقص خاص صفحہ کی درخواست کی ہے.</strong>\n\n{{درست خاص صفحات کی ایک فہرست [[Special:SpecialPages|{{int:specialpages}}]] پر دیکھی جاسکتی ہے}}.",
+ "nospecialpagetext": "<strong>آپ نے ایک غیر موجود خصوصی صفحہ کی درخواست کی ہے۔</strong>\n\nدرست خاص صفحات کی ایک فہرست [[Special:SpecialPages|{{int:specialpages}}]] پر دیکھی جاسکتی ہے۔",
"error": "خطاء",
"databaseerror": "خطائے ڈیٹابیس",
"databaseerror-text": "ڈیٹا بیس کیوری میں خامی پیدا ہوگئی ہے.\nیہ سافٹ ویئر میں ایک مسئلے (بگ) کی نشاندہی کر سکتے ہیں.",
"userlogin-yourname": "صارف نام",
"userlogin-yourname-ph": "اپنا صارف نام درج کریں",
"createacct-another-username-ph": "صارف نام درج کریں",
- "yourpassword": "کلمۂ شناخت",
+ "yourpassword": "پاس ورڈ:",
"userlogin-yourpassword": "کلمۂ شناخت",
"userlogin-yourpassword-ph": "اپنا کلمہ شناخت دیں",
"createacct-yourpassword-ph": "ایک پاس ورڈ داخل کریں",
"throttled-mailpassword": "گزشتہ {{PLURAL:$1|گھنٹے|$1 گھنٹوں}} کے دوران پہلے سے ہی پارلفظ (پاسورڈ) کی تبدیلی کے لیے برقی خط بھیجا گيا ہے۔\nناجائز استعمال کے سدّباب کیلئے، {{PLURAL:$1|گھنٹہ|$1 گھنٹوں}} کے دوران صرف ایک برقی خط بھیجا جاسکتا ہے۔",
"mailerror": "مسلہ دوران ترسیل خط:$1",
"acct_creation_throttle_hit": "آپکی آئی.پی کے ذریعے اِس ویکی پر آنے والے صارفین نے پچھلے ایک دِن میں {{PLURAL:$1|1 کھاتہ بنایا ہے|$1 کھاتے بنائے ہیں}}، جو کہ مذکورہ وقت میں کافی ہیں.\nلہٰذا، آپکی آئی.پی استعمال کرنے والے صارفین اِس وقت مزید کھاتے نہیں بناسکتے.",
- "emailauthenticated": "آپکے برقی ڈاک پتہ کی تصدیق تاریخ $2 بوقت $3 بجے کو ہوئی۔",
+ "emailauthenticated": "آپ کے برقی ڈاک پتہ کی تصدیق مورخہ $2 بوقت $3 بجے ہوئی۔",
"emailnotauthenticated": "آپ کے برقی پتہ کی ابھی تصدیق نہیں ہوئی ہے۔\nدرج ذیل میں سے کسی بھی چیز کیلئے آپ کے برقی پتہ پر برقی ڈاک ارسال نہیں کی جائے گی۔",
"noemailprefs": "اِن خصائص کو کام میں لانے کیلئے اپنے ترجیحات میں برقی ڈاک کا پتہ متعین کیجئے.",
"emailconfirmlink": "اپنے برقی پتہ کی تصدیق کیجئے",
"editingold": "'''انتباہ: آپ اس صفحے کا ایک پرانا مسودہ مرتب کررہے ہیں۔ اگر آپ اسے محفوظ کرتے ہیں تو اس صفحے کے اس پرانے مسودے سے اب تک کی جانے والی تمام تدوین ضائع ہو جاۓ گی۔'''",
"yourdiff": "تضادات",
"copyrightwarning": "یہ یادآوری کرلیجیۓ کہ {{SITENAME}} میں تمام تحریری شراکت جی این یو آزاد مسوداتی اجازہ ($2)کے تحت تصور کی جاتی ہے (مزید تفصیل کیلیۓ $1 دیکھیۓ)۔ اگر آپ اس بات سے متفق نہیں کہ آپکی تحریر میں ترمیمات کری جائیں اور اسے آزادانہ (جیسے ضرورت ہو) استعمال کیا جاۓ تو براۓ کرم اپنی تصانیف یہاں داخل نہ کیجیۓ۔ اگر آپ یہاں اپنی تحریر جمع کراتے ہیں تو آپ اس بات کا بھی اقرار کر رہے ہیں کہ، اسے آپ نے خود تصنیف کیا ہے یا دائرہ ءعام (پبلک ڈومین) سے حاصل کیا ہے یا اس جیسے کسی اور آذاد وسیلہ سے۔'''بلااجازت ایسا کام داخل نہ کیجیۓ جسکا حق ِطبع و نشر محفوظ ہو!'''",
+ "protectedpagewarning": "<strong>انتباہ: اس صفحہ میں ترمیم کاری کو مقفل کر دیا گیا ہے اور محض انتظامی اختیارات کے حامل صارفین ہی اس میں ترمیم کر سکتے ہیں۔</strong>\nحوالہ کے لیے ذیل میں نوشتہ جاتی اندراج فراہم کیا گیا ہے:",
+ "semiprotectedpagewarning": "<strong>اطلاع:</strong> اس صفحہ کو یوں مقفل کیا جاچکا ہے کہ اس میں صرف اندراج شدہ صارفین ہی ترمیم کرسکتے ہیں۔\nحوالہ کے لیے ذیل میں تازہ ترین نوشتہ جاتی اندراج دیا گیا ہے:",
+ "cascadeprotectedwarning": "<strong>انتباہ:</strong> اس صفحہ میں ترمیم کاری کو مقفل کر دیا گیا ہے اور محض انتظامی اختیارات کے حامل صارفین ہی اس میں ترمیم کر سکتے ہیں۔ اسے مقفل کرنے کی وجہ یہ ہے کہ پیش نظر صفحہ درج ذیل محفوظ {{PLURAL:$1|صفحہ|صفحات}} کی آبشاری حفاظت میں شامل ہے:",
"templatesused": "اِس صفحہ پر مستعمل {{PLURAL:$1|سانچہ|سانچے}}:",
"templatesusedpreview": "اِس پیش منظر میں مستعمل {{PLURAL:$1|سانچہ|سانچے}}:",
"templatesusedsection": "اِس قطعہ میں مستعمل {{PLURAL:$1|سانچہ|سانچے}}:",
"viewpagelogs": "اس صفحہ کیلیے نوشتہ جات دیکھیے",
"nohistory": "اِس صفحہ کیلئے کوئی تدوینی تاریخچہ موجود نہیں ہے.",
"currentrev": "حـالیـہ تـجدید",
- "currentrev-asof": "ØاÙ\84Û\8cÛ\81 Ù\86ظرثاÙ\86Û\8c بمطابق $1",
- "revisionasof": "تجدید بمطابق $1",
+ "currentrev-asof": "ØاÙ\84Û\8cÛ\81 Ù\86سخÛ\81 بمطابق $1",
+ "revisionasof": "نسخہ بمطابق $1",
"revision-info": "نظرثانی بتاریخ $1 از {{GENDER:$6|$2}}$7",
"previousrevision": "←پرانی تدوین",
"nextrevision": "→اگلا اعادہ",
"searchdisabled": "{{SITENAME}} تلاش غیرفعال.\nآپ فی الحال گوگل کے ذریعے تلاش کرسکتے ہیں.\nیاد رکھئے کہ اُن کے {{SITENAME}} اشاریے ممکناً پرانے ہوسکتے ہیں.",
"preferences": "ترجیحات",
"mypreferences": "ترجیحات",
- "prefs-edits": "تدÙ\88Û\8cÙ\86ات Ú©Û\8c تعداد:",
+ "prefs-edits": "تعداد تراÙ\85Û\8cÙ\85:",
"prefs-skin": "جِلد",
"skin-preview": "پیش منظر",
- "datedefault": "کوئی ترجیحات نہیں",
+ "datedefault": "کوئی ترجیح نہیں",
"prefs-user-pages": "صارف صفحات",
- "prefs-personal": "Ù\86Ù\85اÛ\8cÛ\82 صارÙ\81",
+ "prefs-personal": "پرÙ\88Ù\81ائÙ\84",
"prefs-rc": "حالیہ تبدیلیاں",
"prefs-watchlist": "زیرِنظر فہرست",
+ "prefs-editwatchlist": "زیر نظر فہرست میں ترمیم کریں",
+ "prefs-editwatchlist-label": "اپنی زیر نظر فہرست کے مندرجات میں ترمیم کریں:",
+ "prefs-editwatchlist-edit": "اپنی زیر نظر فہرست میں عناویں دیکھیں اور حذف کریں",
+ "prefs-editwatchlist-raw": "زیر نظر خام فہرست میں ترمیم کریں",
"prefs-editwatchlist-clear": "اپنی زیر نظر فہرست صاف کریں",
- "prefs-watchlist-days": "زیرِنظر فہرست میں نظر آنے والے ایام:",
- "prefs-watchlist-days-max": "زیادا سے زیادہ $1 {{PLURAL:$1|یوم|ایام}}",
- "prefs-watchlist-edits": "عرÛ\8cض زÛ\8cرÙ\90Ù\86ظرفہرست میں نظر آنے والی تبدیلیوں کی زیادہ سے زیادہ تعداد:",
- "prefs-watchlist-edits-max": "(زیادہ سے زیادہ تعداد: 1000)",
- "prefs-watchlist-token": "کلید زیرنظر فہرست:",
+ "prefs-watchlist-days": "زیر نظر فہرست میں نظر آنے والے ایام:",
+ "prefs-watchlist-days-max": "زیادہ سے زیادہ $1 دن",
+ "prefs-watchlist-edits": "تÙ\88سÛ\8cع شدÛ\81 زÛ\8cر Ù\86ظر فہرست میں نظر آنے والی تبدیلیوں کی زیادہ سے زیادہ تعداد:",
+ "prefs-watchlist-edits-max": "زیادہ سے زیادہ تعداد: 1000",
+ "prefs-watchlist-token": "زیر نظر فہرست کی کلید:",
"prefs-misc": "دیگر",
- "prefs-resetpass": "کلمۂ شناخت تبدیل کیجئے",
+ "prefs-resetpass": "پاس ورڈ تبدیل کریں",
"prefs-changeemail": "برقی ڈاک پتہ (e-mail address) تبدیل کریں",
"prefs-setemail": "برقی پتہ دیں",
- "prefs-email": "اختÛ\8cاراتÙ\90 برÙ\82Û\8c Ú\88اک",
+ "prefs-email": "برÙ\82Û\8c خط Ú©Û\92 اختÛ\8cارات",
"prefs-rendering": "ظاہریت",
"saveprefs": "محفوظ",
- "restoreprefs": "تÙ\85اÙ\85 بÛ\92Ù\86Ù\82ص ترتÛ\8cبات بحال کریں",
- "prefs-editing": "تدÙ\88Û\8cÙ\86",
+ "restoreprefs": "تÙ\85اÙ\85 ابتدائÛ\8c ترتÛ\8cبات Ú©Ù\88 بحال کریں",
+ "prefs-editing": "ترÙ\85Û\8cÙ\85 کارÛ\8c",
"rows": "صفیں:",
"columns": "قطاریں:",
"searchresultshead": "تلاش",
+ "stub-threshold": "نامکمل ربط کے فارمیٹ کی حد ($1):",
"stub-threshold-sample-link": "نمونہ",
"stub-threshold-disabled": "غیر فعال",
- "recentchangesdays": "ØاÙ\84Û\8cÛ\81 تبدÛ\8cÙ\84Û\8cÙ\88Úº Ù\85Û\8cÚº دکھائÛ\8c جانے والے ایّام:",
- "recentchangesdays-max": "(زیادہ سے زیادہ $1 {{PLURAL:$1|دن|ایام}})",
+ "recentchangesdays": "ØاÙ\84Û\8cÛ\81 تبدÛ\8cÙ\84Û\8cÙ\88Úº Ù\85Û\8cÚº دکھائÛ\92 جانے والے ایّام:",
+ "recentchangesdays-max": "زیادہ سے زیادہ $1 دن",
"recentchangescount": "دکھائی جانے والی ترامیم کی تعداد:",
- "prefs-help-recentchangescount": "اِس میں حالیہ تبدیلیاں، تواریخِ صفحہ اور نوشتہ جات شامل ہیں.",
+ "prefs-help-recentchangescount": "اِس میں حالیہ تبدیلیاں، تاریخچے اور نوشتہ جات شامل ہیں۔",
+ "prefs-help-watchlist-token2": "یہ آپ کی زیر نظر فہرست کے ویب فیڈ کی خفیہ کلید ہے۔\nاسے خفیہ رکھیں، تاکہ کوئی دوسرا شخص آپ کی زیر نظر فہرست نہ دیکھ سکے۔\nاگر آپ کو کلید تبدیل کرنی ہو تو [[Special:ResetTokens|یہاں کلک کریں]]۔",
"savedprefs": "آپ کی ترجیحات محفوظ ہوگئیں۔",
"timezonelegend": "منطقۂ وقت:",
"localtime": "مقامی وقت:",
- "servertime": "سرور وقت:",
+ "timezoneuseserverdefault": "ویکی کا طے شدہ استعمال کریں ($1)",
+ "timezoneuseoffset": "دیگر (فرق درج کریں)",
+ "servertime": "سرور کا وقت:",
+ "guesstimezone": "براؤزر کا وقت استعمال کریں",
"timezoneregion-africa": "افریقہ",
"timezoneregion-america": "امریکہ",
"timezoneregion-antarctica": "انٹارکٹیکا",
"prefs-searchoptions": "تلاش",
"prefs-namespaces": "جائے نام",
"default": "طے شدہ",
- "prefs-files": "مسلات",
- "prefs-custom-css": "خودساختہ CSS",
- "prefs-custom-js": "خودساختہ JS",
- "prefs-emailconfirm-label": "برقی پتہ کی تصدیق:",
- "youremail": "٭ برقی خط",
+ "prefs-files": "فائلیں",
+ "prefs-custom-css": "شخصی سی ایس ایس",
+ "prefs-custom-js": "شخصی جاوا اسکرپٹ",
+ "prefs-common-css-js": "جملہ پوشاکوں کے لیے مشترکہ سی ایس ایس/جاوا اسکرپٹ:",
+ "prefs-emailconfirm-label": "برقی خط کی تصدیق:",
+ "youremail": "برقی خط:",
"username": "صارف:",
- "prefs-memberingroups": "{{PLURAL:$1|گروہ|گروہوں}} کا رُکن:",
+ "prefs-memberingroups": "{{PLURAL:$1|گروہ|گروہوں}} {{GENDER:$2|کا رکن|کی رکن}}:",
"prefs-registration": "وقتِ اندراج:",
"yourrealname": "* اصلی نام",
"yourlanguage": "زبان:",
"yourvariant": "متغیّر:",
- "yournick": "دستخط",
+ "yournick": "شخصی دستخط:",
+ "prefs-help-signature": "تبادلۂ خیال صفحات پر تبصرہ تحریر کرنے کے بعد یہ \"<nowiki>~~~~</nowiki>\" علامتیں درج کرنی چاہئیں، یہ علامتیں از خود آپ کے دستخط اور وقت میں تبدیل ہو جائیں گی۔",
"badsig": "ناقص خام دستخط.\nHTML tags جانچئے.",
"badsiglength": "آپ کا دستخط کافی طویل ہے.\nیہ $1 {{PLURAL:$1|حرف|حروف}} سے زیادہ نہیں ہونا چاہئے.",
"yourgender": "جنس:",
- "gender-unknown": "آپ Ú©Û\92 تذکرÛ\81 Ú©Û\92 Ù\88Ù\82تØ\8c سÙ\88Ù\81Ù¹Ù\88Û\8cئر غÛ\8cر جاÙ\86بدار جÙ\86سÛ\8c اÙ\84Ù\81اظ استعÙ\85اÙ\84 کرÛ\92 گا اگر Ù\85Ù\85Ú©Ù\86 Û\81Ù\88",
+ "gender-unknown": "اگر Ù\85Ù\85Ú©Ù\86 Û\81Ù\88 تÙ\88 آپ Ú©Û\92 تذکرÛ\81 Ú©Û\92 Ù\88Ù\82ت ساÙ\81Ù¹ Ù\88Û\8cئر غÛ\8cر جاÙ\86بدار جÙ\86سÛ\8c اÙ\84Ù\81اظ استعÙ\85اÙ\84 کرÛ\92 گا",
"gender-male": "مرد",
"gender-female": "عورت",
- "prefs-help-gender": "اختÛ\8cارÛ\8c: Ù\85صÙ\86عâ\80\8cÙ\84Ø·Û\8cÙ\81 Ú©Û\8c طرÙ\81 سÛ\92 صØÛ\8cØâ\80\8cاÙ\84جÙ\86س تخاطب Ú©Û\8cÙ\84ئÛ\92 استعÙ\85اÙ\84 Û\81Ù\88تا Û\81Û\92. Û\8cÛ\81 Ù\85عÙ\84Ù\88Ù\85ات عاÙ\85 Û\81Ù\88Ú¯Û\8c.",
+ "prefs-help-gender": "اس ترجÛ\8cØ Ú©Û\8c ترتÛ\8cب اختÛ\8cارÛ\8c Û\81Û\92Û\94\nآپ اÙ\88ر دÛ\8cگر صارÙ\81Û\8cÙ\86 Ú©Û\92 Ù\84Û\8cÛ\92 از رÙ\88ئÛ\92 Ù\82Ù\88اعد Ù\85Ù\86اسب جÙ\86سÛ\8c اÙ\84Ù\81اظ Ú©Û\92 اÙ\86تخاب Ú©Û\92 Ù\84Û\8cÛ\92 ساÙ\81Ù¹ Ù\88Û\8cئر اس Ù\82در Ú©Ù\88 استعÙ\85اÙ\84 کرتا Û\81Û\92Û\94\nÛ\8cÛ\81 Ù\85عÙ\84Ù\88Ù\85ات عاÙ\85 Û\81Ù\88Ú¯Û\8cÛ\94",
"email": "برقی خط",
- "prefs-help-realname": "ØÙ\82Û\8cÙ\82Û\8c Ù\86اÙ\85 اختÛ\8cارÛ\8c Û\81Û\92Û\94\nاگر آپ اسÛ\92 Ù\85Û\81Û\8cÙ\91ا کرتÛ\92 Û\81Û\8cÚºØ\8c تÙ\88 اسÛ\92 آپ Ú©Û\92 کاÙ\85 Ú©Û\8cÙ\84ئÛ\92 آپ Ú©Ù\88 اÙ\86تساب دÛ\8cÙ\86Û\92 Ú©Û\8cÙ\84ئے استعمال کیا جائے گا۔",
- "prefs-help-email": "برقی ڈاک کا پتہ اختیاری ہے، لیکن یہ اُس وقت مفید ثابت ہوسکتا ہے جب آپ اپنا پارلفظ بھول گئے ہوں.",
- "prefs-help-email-others": "آپ یہ بھی منتخب کرسکتے ہیں کہ دوسرے صارفین آپ کے تبادلۂ خیال صفحہ پر ایک ربط کے ذریعے آپ کو برقی ڈاک بھیجیں.\nجب دوسرے صارفین آپ سے رابطہ کرتے ہیں تو آپ کا برقی ڈاک کا پتہ افشا نہیں کیا جاتا۔",
+ "prefs-help-realname": "ØÙ\82Û\8cÙ\82Û\8c Ù\86اÙ\85 اختÛ\8cارÛ\8c Û\81Û\92Û\94\nاگر آپ درج کرÛ\8cÚº تÙ\88 اسÛ\92 آپ Ú©Û\92 کاÙ\85Ù\88Úº Ú©Ù\88 آپ سÛ\92 Ù\85Ù\86سÙ\88ب کرÙ\86Û\92 Ú©Û\92 Ù\84Û\8cے استعمال کیا جائے گا۔",
+ "prefs-help-email": "برقی ڈاک پتے کا اندراج اختیاری ہے، عموماً اس کی ضرورت اس وقت پڑتی ہے جب آپ اپنا پاس ورڈ بھول چکے ہوں اور نیا پاس ورڈ رکھنا چاہتے ہوں۔",
+ "prefs-help-email-others": "یہ ممکن ہے کہ آپ دیگر صارفین کو اس بات کی اجازت دیں کہ وہ آپ کے صارف یا تبادلۂ خیال صفحہ پر موجود ربط کے ذریعہ آپ کو برقی خط بھیج سکیں۔\nجب صارفین اس طرح آپ سے رابطہ کریں گے تو انہیں آپ کا برقی ڈاک پتہ نظر نہیں آئے گا۔",
"prefs-help-email-required": "برقی ڈاک پتہ چاہئے.",
"prefs-info": "بنیادی معلومات",
"prefs-i18n": "بین الاقوامیت",
"prefs-signature": "دستخط",
- "prefs-dateformat": "Ø´Ú©Ù\84بÙ\86دÙ\90 تارÛ\8cØ®",
+ "prefs-dateformat": "تارÛ\8cØ® Ú©Û\8c ترتÛ\8cب",
"prefs-timeoffset": "وقت کی ترتیب",
"prefs-advancedediting": "اعلی اختیارات",
"prefs-editor": "خانہ ترمیم",
"prefs-advancedrendering": "اعلی اختیارات",
"prefs-advancedsearchoptions": "اعلی اختیارات",
"prefs-advancedwatchlist": "اعلی اختیارات",
+ "prefs-displayrc": "نمائش کے اختیارات",
+ "prefs-displaywatchlist": "نمائش کے اختیارات",
"prefs-tokenwatchlist": "ٹوکن",
- "prefs-diffs": "Ù\81رÙ\88Ù\82",
+ "prefs-diffs": "فرق",
"userrights": "حقوقِ صارف کی نظامت",
"userrights-lookup-user": "گروہائے صارف کا انتظام",
"userrights-user-editname": "کوئی اسمصارف داخل کیجئے:",
"recentchanges-legend-heading": "<strong>اختیارات</strong>",
"recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (نیز [[Special:NewPages|جدید صفحات کی فہرست]]) ملاحظہ فرمائیں",
"recentchanges-submit": "دکھائیں",
- "rcnotefrom": "ذیل میں <strong>$3, $4</strong> سے کی گئی {{PLURAL:$5|تبدیلی|تبدیلیاں}} <strong>$1</strong> تک دکھائی جا رہی ہیں۔",
- "rclistfrom": "$3 $2 سےنئی تبدیلیاں دکھانا شروع کریں",
+ "rcnotefrom": "ذیل میں <strong>$2</strong> سے کی گئی {{PLURAL:$5|تبدیلی|تبدیلیاں}} <strong>$1</strong> تک دکھائی جا رہی ہیں۔",
+ "rclistfrom": "$2، $3ء سے ہونے والی نئی تبدیلیاں دکھائیں",
"rcshowhideminor": "معمولی ترامیم $1",
"rcshowhideminor-show": "دکھائیں",
"rcshowhideminor-hide": "چھپائیں",
"recentchanges-page-added-to-category-bundled": "[[:$1]] اور {{PLURAL:$2|ایک صفحہ|$2 صفحات}} زمرہ میں شامل {{PLURAL:$2|کیا گیا|$2 کیے گئے}}",
"recentchanges-page-removed-from-category": "[[:$1]] کو زمرہ سے ہٹایا",
"autochange-username": "میڈیاویکی خودکار تبدیلیاں",
- "upload": "فائل اثقال/اپلوڈ فائل",
+ "upload": "اپلوڈ",
"uploadbtn": "زبراثقال ملف (اپ لوڈ فائل)",
"reuploaddesc": "زبراثقال ورقہ (فارم) کیجانب واپس۔",
"uploadnologin": "آپ داخل شدہ حالت میں نہیں",
"randomincategory-category": "زمرہ:",
"randomincategory-submit": "جانا",
"statistics": "اعداد و شمار",
- "statistics-header-pages": "اØصائÛ\92 صÙ\81Øات",
- "statistics-header-edits": "اØصائÛ\92 تدÙ\88Û\8cÙ\86",
+ "statistics-header-pages": "صÙ\81Øات Ú©Û\92 اعداد Ù\88 Ø´Ù\85ار",
+ "statistics-header-edits": "ترÙ\85Û\8cÙ\85Û\8c اعداد Ù\88 Ø´Ù\85ار",
"statistics-header-users": "ارکان کے اعداد و شمار",
- "statistics-header-hooks": "اØصائÛ\92 دÛ\8cÚ¯ر",
+ "statistics-header-hooks": "دÛ\8cگر اعداد Ù\88 Ø´Ù\85ار",
"statistics-articles": "مندرج صفحات",
"statistics-pages": "صفحات",
"statistics-pages-desc": "(ویکی اقتباسات کے کل صفحات، بشمولِ تبادلۂ خیال، رجوع مکررات وغیرہ۔)",
- "statistics-files": "زبراثÙ\82اÙ\84 شدÛ\81 Ù\85Ù\84Ù\81ات",
- "statistics-edits": "ویکی اقتباسات کے آغاز سے کل صفحاتی ترمیم",
+ "statistics-files": "اپÙ\84Ù\88Ú\88 کردÛ\81 Ù\81ائÙ\84Û\8cÚº",
+ "statistics-edits": "{{SITENAME}} کے آغاز سے کل صفحاتی ترامیم",
"statistics-edits-average": "فی صفحہ اوسط ترامیم",
"statistics-users": "مندرج [[خاص:فہرست صارفین، صارف فہرست|صارفین]]",
"statistics-users-active": "متحرک صارفین",
"deleteotherreason": "دوسری/اِضافی وجہ:",
"deletereasonotherlist": "دوسری وجہ",
"rollback": "ترمیمات سابقہ حالت پرواپس",
- "rollbacklink": "واپس سابقہ حالت",
+ "rollbacklink": "استرجع کریں",
"rollbacklinkcount": "استرجع $1 {{PLURAL:$1|ترمیم|ترامیم}}",
+ "rollbacklinkcount-morethan": "$1 {{PLURAL:$1|ترمیم|ترامیم}} سے زیادہ کا استرجع",
"rollbackfailed": "سابقہ حالت پر واپسی ناکام",
"cantrollback": "تدوین ثانی کا اعادہ نہیں کیا جاسکتا؛ کیونکہ اس میں آخری بار حصہ لینے والا ہی اس صفحہ کا واحد کاتب ہے۔",
"changecontentmodel-title-label": "صفحہ کا عنوان",
"blocklink": "پابندی لگائیں",
"unblocklink": "پابندی ختم",
"change-blocklink": "پابندی میں تبدیلی",
- "contribslink": "شراکت",
+ "contribslink": "شراکتیں",
"blocklogpage": "نوشتۂ پابندی",
"block-log-flags-nocreate": "کھاتے کی تخلیق غیرفعال",
"move-page": "منتقلی $1",
"tooltip-rollback": "پچھلے صارف کی کی گئی اِس صفحے پر استرجع شدہ ترامیم کو ایک کلِک میں واپس کریں",
"tooltip-undo": "''استرجع'' اس ترمیم کو پچھلی ترمیم کے جانب واپس کردیگا اور نمائشی انداز میں خانہ ترمیم کھول دے گا۔ آپ مختصراً سبب بیان کرنے کے بھی مجاز ہونگے۔",
"tooltip-summary": "مختصر خلاصہ درج کریں",
+ "common.css": "body,\ntextarea {\n font-family: Amiri;\n}",
"anonymous": "{{SITENAME}} گمنام صارف",
"others": "دیگر",
"pageinfo-visiting-watchers": "تعداد ناظرین جنہوں نے حالیہ ترامیم کا مشاہدہ کیا",
"deletedrevision": "حذف شدہ پرانی ترمیم $1۔",
"previousdiff": "← پُرانی تدوین",
"nextdiff": "صفحہ کا نام:",
+ "imagemaxsize": "تصویر کی جسامت کی حد:<br /><em>(فائل کے توضیحی صفحات کے لیے)</em>",
+ "thumbsize": "تھمب نیل کی جسامت:",
"file-info-size": "\n$1 × $2 عکصر (پکسلز)، حجم ملف: $3، MIME قسم: $4",
"file-nohires": "اس سے بڑی تصمیم دستیاب نہیں۔",
"show-big-image": "اصل ملف",
"confirm_purge_button": "جی!",
"confirm-rollback-button": "ٹھیک ہے",
"semicolon-separator": "؛ ",
+ "comma-separator": "، ",
"imgmultipageprev": "← پچھلا",
"imgmultipagenext": "اگلا →",
"imgmultigo": "جائیں!",
"autosumm-blank": "تمام مندرجات حذف",
"autoredircomment": "[[$1]] سے رجوع مکرر",
"autosumm-new": "نیا صفحہ: $1",
+ "size-bytes": "$1 بائٹ",
"watchlisttools-view": "متعلقہ تبدیلیاں دیکھیں",
"watchlisttools-edit": "زیرِنظرفہرست دیکھیں اور تدوین کریں",
"watchlisttools-raw": "خام زیرِنظرفہرست تدوین کریں",
"logentry-move-move": "$1 نے صفحہ $3 کو $4 کی جانب منتقل کیا",
"logentry-newusers-create": "صارف کھاتہ $1 {{GENDER:$2|بنایا گیا}}",
"logentry-protect-move_prot": "$1 نے ترتیب درجہ حفاظت $4 سے $3 کی طرف {{GENDER:$2|منتقل کی}}",
+ "logentry-protect-protect": "$1 نے $3 کو {{GENDER:$2|محفوظ کیا}} $4",
"logentry-protect-modify": "$1 نے $3 کا درجۂ حفاظت {{GENDER:$2|تبدیل کیا}} $4",
"logentry-rights-rights": "$1 نے {{GENDER:$6|$3}} کی گروہی رکنیت از $4 تا $5 {{GENDER:$2|تبدیل کی}}",
"logentry-upload-upload": "$1 {{GENDER:$2|اپلوڈ}} $3",
"createacct-another-username-ph": "请输入用户名",
"yourpassword": "密码:",
"userlogin-yourpassword": "密码",
- "userlogin-yourpassword-ph": "请输入你的密码",
+ "userlogin-yourpassword-ph": "请输入您的密码",
"createacct-yourpassword-ph": "请输入密码",
"yourpasswordagain": "请再次输入密码:",
"createacct-yourpasswordagain": "确认密码",
"createacct-yourpasswordagain-ph": "请再次输入密码",
"userlogin-remembermypassword": "记住我的登录状态",
"userlogin-signwithsecure": "使用安全连接",
+ "cannotlogin-title": "不能登录",
+ "cannotlogin-text": "无法登录。",
"cannotloginnow-title": "现在不能登录",
"cannotloginnow-text": "当使用$1时无法登录。",
+ "cannotcreateaccount-title": "无法创建账户",
+ "cannotcreateaccount-text": "此wiki没有启用直接账户创建。",
"yourdomainname": "您的域名:",
"password-change-forbidden": "您不能在本wiki上更改密码。",
"externaldberror": "验证数据库出错或您被禁止更新您的外部账号。",
"botpasswords-updated-body": "用于用户“$2”的机器人名称“$1”的机器人密码已更新。",
"botpasswords-deleted-title": "机器人密码已删除",
"botpasswords-deleted-body": "用于用户“$2”的机器人名称“$1”的机器人密码已删除。",
- "botpasswords-newpassword": "用于登录<strong>$1</strong>的新密码是<strong>$2</strong>。<em>请记住它以备今后参考。</em>",
+ "botpasswords-newpassword": "用于登录<strong>$1</strong>的新密码是<strong>$2</strong>。<em>请记住它以备今后参考。</em><br>(对于需要登录名与最终用户名相同的旧机器人,您也可以使用<strong>$3</strong>作为用户名,<strong>$4</strong>作为密码。)",
"botpasswords-no-provider": "BotPasswordsSessionProvider不可用。",
"botpasswords-restriction-failed": "机器人密码限制阻止此次登录。",
"botpasswords-invalid-name": "指定的用户名不包含机器人密码分隔符(“$1”)。",
"invalid-content-data": "无效的内容数据",
"content-not-allowed-here": "[[$2]]页面上不允许“$1”内容",
"editwarning-warning": "离开本页面可能导致您失去任何你已经作出的更改。如果您处于登录状态,您可以在您的设置的“{{int:prefs-editing}}”部分停用该警告。",
+ "editpage-invalidcontentmodel-title": "内容模型不支持",
+ "editpage-invalidcontentmodel-text": "内容模型“$1”不被支持。",
"editpage-notsupportedcontentformat-title": "内容格式尚不支持",
"editpage-notsupportedcontentformat-text": "内容模型$2尚不支持内容格式$1。",
"content-model-wikitext": "维基文字",
"tooltip-ca-nstab-help": "查看帮助页面",
"tooltip-ca-nstab-category": "查看分类页面",
"tooltip-minoredit": "标记本编辑为小编辑",
- "tooltip-save": "保存你的更改",
+ "tooltip-save": "保存您的更改",
"tooltip-publish": "发布您的更改",
"tooltip-preview": "预览您的更改。请在保存前使用此功能。",
"tooltip-diff": "显示您对该文字所做的更改",
"pageinfo-article-id": "页面ID",
"pageinfo-language": "页面内容语言",
"pageinfo-content-model": "页面内容类型",
+ "pageinfo-content-model-change": "更改",
"pageinfo-robot-policy": "爬虫索引",
"pageinfo-robot-index": "允许",
"pageinfo-robot-noindex": "不允许",
"tag-filter": "[[Special:Tags|标签]]过滤器:",
"tag-filter-submit": "过滤器",
"tag-list-wrapper": "([[Special:Tags|$1个标签]]:$2)",
+ "tag-mw-contentmodelchange": "内容模型更改",
+ "tag-mw-contentmodelchange-description": "更改页面[https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel 内容模型]的编辑",
"tags-title": "标签",
"tags-intro": "本页面列出了软件可能用于标记编辑的标签和它们的含义。",
"tags-tag": "标签名称",
"tags-actions-header": "操作",
"tags-active-yes": "是",
"tags-active-no": "否",
- "tags-source-extension": "由一个扩展定义",
+ "tags-source-extension": "由软件定义",
"tags-source-manual": "可被用户和机器人手动应用",
"tags-source-none": "不再被使用",
"tags-edit": "编辑",
"Reke",
"Kly",
"Cosine02",
- "一個正常人"
+ "一個正常人",
+ "Wehwei"
]
},
"tog-underline": "底線標示連結:",
"createacct-yourpasswordagain-ph": "再次輸入密碼",
"userlogin-remembermypassword": "記住我的登入狀態",
"userlogin-signwithsecure": "使用安全連線",
+ "cannotlogin-title": "無法登入",
+ "cannotlogin-text": "無法登入",
"cannotloginnow-title": "現在無法登入",
"cannotloginnow-text": "使用 $1 時無法登入。",
+ "cannotcreateaccount-title": "無法建立帳號",
"yourdomainname": "您的網域:",
"password-change-forbidden": "您不可變更此 Wiki 上的密碼。",
"externaldberror": "這可能是由於資料庫驗證錯誤,或是不允許您更新外部帳號。",
"grant-group-high-volume": "執行大量活動",
"grant-group-customization": "自訂與偏好設定",
"grant-group-administration": "執行管理操作",
+ "grant-group-private-information": "存取關於您的隱私資料",
"grant-group-other": "其他活動",
"grant-blockusers": "封鎖與解除封鎖使用者",
"grant-createaccount": "建立帳號",
"grant-highvolume": "大量編輯",
"grant-oversight": "隱藏使用者和禁止顯示修訂",
"grant-patrol": "巡邏頁面的變更",
+ "grant-privateinfo": "存取隱私資訊",
"grant-protect": "保護與取消保護頁面",
"grant-rollback": "還原頁面的變更",
"grant-sendemail": "傳送電子郵件聯絡其他使用者",
"file-thumbnail-no": "檔案名稱以 <strong>$1</strong> 為開頭。\n似乎已為縮小的圖片 <em>(縮圖)</em>。\n若您有原始大小的圖片,應上傳原始圖片,否則請變更檔名稱。",
"fileexists-forbidden": "已存在相同名稱的檔案,且無法覆蓋。\n若您仍要上傳此檔案,請返回上一頁並使用其他名稱。\n[[File:$1|thumb|center|$1]]",
"fileexists-shared-forbidden": "共用檔案庫中已存在此名稱的檔案。\n若您仍要上傳此檔案,請返回上一頁並使用其他名稱。\n[[File:$1|thumb|center|$1]]",
+ "fileexists-no-change": "上傳的檔案與目前版本的 <strong>[[:$1]]</strong> 完全相同。",
+ "fileexists-duplicate-version": "上傳的檔案與{{PLURAL:$2|較舊版本|較舊版本}}的 <strong>[[:$1]]</strong> 完全相同。",
"file-exists-duplicate": "此檔案與下列{{PLURAL:$1|一|多}}個檔案重複:",
"file-deleted-duplicate": "與此檔案完全相同的檔案 ([[:$1]]) 在先前已被刪除。\n您應在重新上傳之前確認該檔案的刪除日誌。",
"file-deleted-duplicate-notitle": "與此檔案完全相同的檔案在先前已被刪除,且禁止顯示該標題。\n您在重新上傳前,應請求有權力檢視隱藏檔案的使用者重新審查。",
"filerevert-submit": "還原",
"filerevert-success": "<strong>[[Media:$1|$1]]</strong> 已經還原到 [$4 於 $2 $3 的版本]。",
"filerevert-badversion": "查無此檔案先前於指定時間的本地版本。",
+ "filerevert-identical": "目前版本的檔案與選擇的版本完全相同。",
"filedelete": "刪除 $1",
"filedelete-legend": "刪除檔案",
"filedelete-intro": "您現正要刪除檔案 <strong>[[Media:$1|$1]]</strong> 與其所有歷史版本。",
"watchnologin": "尚未登入",
"addwatch": "新增至監視清單",
"addedwatchtext": "已於[[Special:Watchlist|您的監視清單]]新增頁面 \"[[:$1]]\" 及其討論頁面。\n未來對此頁面及其關聯的對話頁面的變更將會在此清單中列出。",
+ "addedwatchtext-talk": "\"[[:$1]]\" 及相關的頁面已加入至您的 [[Special:Watchlist|監視清單]]。",
"addedwatchtext-short": "已於您的監視清單新增頁面 \"$1\"。",
"removewatch": "從監視清單中移除",
"removedwatchtext": "已於[[Special:Watchlist|您的監視清單]]移除頁面 \"[[:$1]]\" 及其討論頁面。",
+ "removedwatchtext-talk": "已自您的 [[Special:Watchlist|監視清單]] 移除 \"[[:$1]]\" 及相關的頁面。",
"removedwatchtext-short": "已於您的監視清單移除頁面 \"$1\"。",
"watch": "監視",
"watchthispage": "監視此頁面",
"rollbacklinkcount-morethan": "還原超過 $1 次{{PLURAL:$1|編輯}}",
"rollbackfailed": "還原失敗",
"rollback-missingparam": "請求缺少必要參數。",
+ "rollback-missingrevision": "無法載入修訂資料。",
"cantrollback": "無法還原編輯;\n此頁面的最後貢獻者是唯一的作者。",
"alreadyrolled": "無法還原由 [[User:$2|$2]] ([[User talk:$2|對話]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]] 所作的最後一次編輯 [[:$1]],已有其他人編輯或還原了該頁面。\n\n最後一次編輯該頁面的使用者是 [[User:$3|$3]] ([[User talk:$3|對話]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]])。",
"editcomment": "編輯摘要為:<em>$1</em>。",
"undeletehistorynoadmin": "已刪除此頁面。\n以下摘要顯示刪除原因與刪除前所有編輯過此頁面的使用者詳細資料。\n這些已刪除的實際文字修訂僅對管理員可用。",
"undelete-revision": "被 $3 刪除的 $1 (於 $4 $5) 修訂:",
"undeleterevision-missing": "無效或遺失的修訂。\n您可能使用了錯誤的連結,或該修訂已從封存中還原或刪除。",
+ "undeleterevision-duplicate-revid": "無法還原 {{PLURAL:$1|1 個修訂|$1 修訂}},因{{PLURAL:$1|修訂的|修訂的}} <code>rev_id</code> 已在使用中。",
"undelete-nodiff": "查無先前的修訂。",
"undeletebtn": "還原",
"undeletelink": "檢視/還原",
"undeletedrevisions": "{{PLURAL:$1|$1 個修訂}}已還原",
"undeletedrevisions-files": "{{PLURAL:$1|$1 個修訂}}與 {{PLURAL:$2|$2 個檔案}}已還原",
"undeletedfiles": "{{PLURAL:$1|$1}} 個檔案已還原",
- "cannotundelete": "取消刪除失敗:\n$1",
+ "cannotundelete": "部份或全部的取消刪除失敗:\n$1",
"undeletedpage": "<strong>$1 已還原</strong>\n\n請參考 [[Special:Log/delete|刪除日誌]] 以查詢最近刪除及還原的記錄。",
"undelete-header": "請參考 [[Special:Log/delete|刪除日誌]] 查詢最近刪除的頁面。",
"undelete-search-title": "搜尋已刪除頁面",
"pageinfo-article-id": "頁面 ID",
"pageinfo-language": "頁面內容語言",
"pageinfo-content-model": "頁面內容模型",
+ "pageinfo-content-model-change": "變更",
"pageinfo-robot-policy": "由機器人建立索引",
"pageinfo-robot-index": "允許",
"pageinfo-robot-noindex": "不允許",
"tags-actions-header": "操作",
"tags-active-yes": "是",
"tags-active-no": "否",
- "tags-source-extension": "由擴充套件定義",
+ "tags-source-extension": "由軟體定義",
"tags-source-manual": "由使用者與機器人手動套用",
"tags-source-none": "不再使用",
"tags-edit": "編輯",
"linkaccounts-submit": "連結帳號",
"unlinkaccounts": "取消連結帳號",
"unlinkaccounts-success": "已取消連結帳號。",
- "authenticationdatachange-ignored": "認証資料變更未被處理,可能未設定提供者?"
+ "authenticationdatachange-ignored": "認証資料變更未被處理,可能未設定提供者?",
+ "userjsispublic": "請注意:JavaScript 子頁面可被其他使用者檢視,不應包含憑証資料。",
+ "usercssispublic": "請注意:CSS 子頁面可被其他使用者檢視,不應包含憑証資料。"
}
* @author Yanteng3
*/
+$fallback = 'zh-hant'; // T125373
+
$specialPageAliases = [
'Activeusers' => [ '躍簿' ],
'Allmessages' => [ '官話' ],
$rtl = true;
$namespaceNames = [
- NS_MEDIA => 'Ù\88سÛ\8cØ·',
+ NS_MEDIA => 'Ù\85Û\8cÚ\88Û\8cا',
NS_SPECIAL => 'خاص',
NS_TALK => 'تبادلۂ_خیال',
NS_USER => 'صارف',
NS_USER_TALK => 'تبادلۂ_خیال_صارف',
NS_PROJECT_TALK => 'تبادلۂ_خیال_$1',
- NS_FILE => 'Ù\85Ù\84Ù\81',
- NS_FILE_TALK => 'تبادÙ\84Û\82_Ø®Û\8cاÙ\84_Ù\85Ù\84Ù\81',
+ NS_FILE => 'Ù\81ائÙ\84',
+ NS_FILE_TALK => 'تبادÙ\84Û\82_Ø®Û\8cاÙ\84_Ù\81ائÙ\84',
NS_MEDIAWIKI => 'میڈیاویکی',
NS_MEDIAWIKI_TALK => 'تبادلۂ_خیال_میڈیاویکی',
NS_TEMPLATE => 'سانچہ',
];
$namespaceAliases = [
+ 'وسیط' => NS_MEDIA,
'زریعہ' => NS_MEDIA,
'تصویر' => NS_FILE,
'تبادلۂ_خیال_تصویر' => NS_FILE_TALK,
+ 'ملف' => NS_FILE,
+ 'تبادلۂ_خیال_ملف' => NS_FILE_TALK,
'میڈیاوکی' => NS_MEDIAWIKI,
'تبادلۂ_خیال_میڈیاوکی' => NS_MEDIAWIKI_TALK,
];
'Ancientpages' => [ 'قدیم_صفحات' ],
'Badtitle' => [ 'خراب_عنوان' ],
'Blankpage' => [ 'خالی_صفحہ' ],
- 'Block' => [ 'پابندی', 'آئی_پی_پتہ_پابندی،_پابندی_بر_صارف' ],
+ 'Block' => [ 'پابندی', 'آئی_پی_پتہ_پابندی', 'پابندی_بر_صارف' ],
'Booksources' => [ 'کتابی_وسائل' ],
'BrokenRedirects' => [ 'شکستہ_رجوع_مکررات' ],
'Categories' => [ 'زمرہ_جات' ],
'DoubleRedirects' => [ 'دوہرے_رجوع_مکررات' ],
'EditWatchlist' => [ 'ترمیم_زیر_نظر' ],
'Emailuser' => [ 'صارف_ڈاک' ],
- 'Export' => [ 'برآمدگی' ],
+ 'Export' => [ 'برآمد', 'برآمدگی' ],
'Fewestrevisions' => [ 'کم_نظر_ثانی_شدہ' ],
- 'FileDuplicateSearch' => [ 'دہری_ملف_تلاش' ],
- 'Filepath' => [ 'راہ_ملف' ],
- 'Import' => [ 'درآمدگی' ],
+ 'FileDuplicateSearch' => [ 'تÙ\84اش_دÙ\88Û\81رÛ\8c_Ù\81ائÙ\84', 'دÛ\81رÛ\8c_Ù\85Ù\84Ù\81_تÙ\84اش' ],
+ 'Filepath' => [ 'راÛ\81_Ù\81ائÙ\84', 'راÛ\81_Ù\85Ù\84Ù\81' ],
+ 'Import' => [ 'درآمد', 'درآمدگی' ],
'Invalidateemail' => [ 'ڈاک_تصدیق_منسوخ' ],
'JavaScriptTest' => [ 'تجربہ_جاوا_اسکرپٹ' ],
'BlockList' => [ 'فہرست_ممنوع', 'فہرست_دستور_شبکی_ممنوع' ],
'LinkSearch' => [ 'تلاش_روابط' ],
'Listadmins' => [ 'فہرست_منتظمین' ],
'Listbots' => [ 'فہرست_روبہ_جات' ],
- 'Listfiles' => [ 'فہرست_املاف', 'فہرست_تصاویر' ],
+ 'Listfiles' => [ 'فائلوں_کی_فہرست', 'فہرست_تصاویر' ],
'Listgrouprights' => [ 'فہرست_اختیارات_گروہ', 'صارفی_گروہ_اختیارات' ],
'Listredirects' => [ 'فہرست_رجوع_مکررات' ],
- 'Listusers' => [ 'فہرست_صارفین،_صارف_فہرست' ],
+ 'Listusers' => [ 'فہرست_صارفین' ],
'Log' => [ 'نوشتہ', 'نوشتہ_جات' ],
'Lonelypages' => [ 'یتیم_صفحات' ],
'Longpages' => [ 'طویل_صفحات' ],
'MergeHistory' => [ 'ضم_تاریخچہ' ],
'Movepage' => [ 'منتقلی_صفحہ' ],
- 'Mycontributions' => [ 'میرا_حصہ' ],
+ 'Mycontributions' => [ 'میری_شراکتیں', 'میرا_حصہ' ],
'Mypage' => [ 'میرا_صفحہ' ],
'Mytalk' => [ 'میری_گفتگو' ],
- 'Myuploads' => [ 'میرے_زبراثقالات' ],
- 'Newimages' => [ 'جدید_املاف', 'جدید_تصاویر' ],
+ 'Myuploads' => [ 'Ù\85Û\8cرÛ\92_اپÙ\84Ù\88Ú\88', 'Ù\85Û\8cرÛ\92_زبراثÙ\82اÙ\84ات' ],
+ 'Newimages' => [ 'جدید_فائلیں', 'جدید_املاف', 'جدید_تصاویر' ],
'Newpages' => [ 'جدید_صفحات' ],
'PermanentLink' => [ 'مستقل_ربط' ],
'Preferences' => [ 'ترجیحات' ],
'Randomredirect' => [ 'تصادفی_رجوع_مکرر' ],
'Recentchanges' => [ 'حالیہ_تبدیلیاں' ],
'Recentchangeslinked' => [ 'متعلقہ_تبدیلیاں' ],
- 'Revisiondelete' => [ 'حذف_اعادہ' ],
+ 'Revisiondelete' => [ 'حذف_نظر_ثانی', 'حذف_اعادہ' ],
'Search' => [ 'تلاش' ],
'Shortpages' => [ 'مختصر_صفحات' ],
'Specialpages' => [ 'خصوصی_صفحات' ],
'Statistics' => [ 'شماریات' ],
- 'Tags' => [ 'ٹیگز' ],
+ 'Tags' => [ 'ٹیگ', 'ٹیگز' ],
'Unblock' => [ 'پابندی_ختم' ],
'Uncategorizedcategories' => [ 'غیر_زمرہ_بند_زمرہ_جات' ],
- 'Uncategorizedimages' => [ 'غیر_زمرہ_بند_املاف', 'غیر_زمرہ_بند_تصاویر' ],
+ 'Uncategorizedimages' => [ 'غیر_زمرہ_بند_فائلیں', 'غیر_زمرہ_بند_املاف', 'غیر_زمرہ_بند_تصاویر' ],
'Uncategorizedpages' => [ 'غیر_زمرہ_بند_صفحات' ],
'Uncategorizedtemplates' => [ 'غیر_زمرہ_بند_سانچے' ],
'Undelete' => [ 'بحال' ],
'Unusedcategories' => [ 'غیر_مستعمل_زمرہ_جات' ],
- 'Unusedimages' => [ 'غیر_مستعمل_املاف', 'غیر_مستعمل_تصاویر' ],
+ 'Unusedimages' => [ 'غیر_مستعمل_فائلیں', 'غیر_مستعمل_املاف', 'غیر_مستعمل_تصاویر' ],
'Unusedtemplates' => [ 'غیر_مستعمل_سانچے' ],
'Unwatchedpages' => [ 'نادیدہ_صفحات' ],
- 'Upload' => [ 'زبراثقال' ],
+ 'Upload' => [ 'اپÙ\84Ù\88Ú\88', 'زبراثÙ\82اÙ\84' ],
'Userlogin' => [ 'داخل_نوشتگی' ],
'Userlogout' => [ 'خارج_نوشتگی' ],
'Userrights' => [ 'صارفی_اختیارات' ],
- 'Version' => [ 'اخراجہ' ],
+ 'Version' => [ 'نسخہ', 'اخراجہ' ],
'Wantedcategories' => [ 'مطلوبہ_زمرہ_جات' ],
- 'Wantedfiles' => [ 'مطلوبہ_املاف' ],
+ 'Wantedfiles' => [ 'مطلوبہ_فائلیں', 'مطلوبہ_املاف' ],
'Wantedpages' => [ 'مطلوبہ_صفحات', 'شکستہ_روابط' ],
'Wantedtemplates' => [ 'مطلوبہ_سانچے' ],
'Watchlist' => [ 'زیر_نظر_فہرست' ],
- 'Whatlinkshere' => [ 'یہاں_کس_کا_رابطہ' ],
+ 'Whatlinkshere' => [ 'مربوط_صفحات', 'یہاں_کس_کا_رابطہ' ],
'Withoutinterwiki' => [ 'بدون_بین_الویکی' ],
];
return fgets( STDIN, 1024 );
}
+
+ /**
+ * Call this to set up the autoloader to allow classes to be used from the
+ * tests directory.
+ */
+ public static function requireTestsAutoloader() {
+ require_once __DIR__ . '/../tests/common/TestsAutoLoader.php';
+ }
}
/**
@echo "Run 'make man' to run the doxygen generation with man pages."
test:
- php tests/parserTests.php --quiet
+ php tests/parser/parserTests.php --quiet
doc:
php mwdocgen.php --all
// NOTE (phuedx, 2014-03-26) wgAutoloadClasses isn't set up
// by either of the dependencies at the top of the file, so
// require it here.
- require_once __DIR__ . '/../tests/TestsAutoLoader.php';
+ self::requireTestsAutoloader();
// If phpunit isn't available by autoloader try pulling it in
if ( !class_exists( 'PHPUnit_Framework_TestCase' ) ) {
// this could be done some other, more direct/efficient way, but using
// UploadStash's own methods means it's less likely to fall accidentally
// out-of-date someday
- $stash = new UploadStash( $repo, new User() );
+ $stash = new UploadStash( $repo );
$i = 0;
foreach ( $keys as $key ) {
// Commit and close up!
$factory = wfGetLBFactory();
$factory->commitMasterChanges( 'doMaintenance' );
-$factory->shutdown();
+$factory->shutdown( $factory::SHUTDOWN_NO_CHRONPROT );
一地里 一地裏
一年里 一年裏
中文里 中文裏
+英文里 英文裏
+古文里 古文裏
+经文里 經文裏
+论文里 論文裏
+譯文里 譯文裏
+原文里 原文裏
+正文里 正文裏
+下文里 下文裏
+条文里 條文裏
+画里 畫裏
事里 事裏
井里 井裏
作品里 作品裏
会里 會裏
村里的 村裏的
村里有 村裏有
+区里的 區裏的
+区里有 區裏有
森林里 森林裏
棺材里 棺材裏
树林里 樹林裏
划着独木舟 划著獨木舟
着眼于 著眼於
桃金娘 桃金孃
+粘膜 黏膜
缺省 預設
以太网 乙太網
光盘 光碟
撒切尔 柴契爾
戴卓爾 柴契爾
摩根士丹利 摩根史坦利
-拉普兰 拉布蘭
戴克里先 戴克里先
戈爾巴喬夫 戈巴契夫
戈尔巴乔夫 戈巴契夫
冬冬鼓 鼕鼕鼓
苧麻 苧麻
张柏芝 張栢芝
+杜琪峰 杜琪峯
皺彆
一彆頭
并州
+幽并
併力
,並力
,并力討
扁擬穀盜蟲
不穀
辟穀
-米穀
-田穀
脫穀機
年穀
礱穀
月球曆表
伊爾汗曆表
延曆
+萬曆
+永曆
+聖人曆
+羅馬曆
+羅馬歷史
+羅馬歷代
+曆數書
+曆局
+授時曆
+顓頊曆
共和歷史
厤物之意
爰定祥厤
磨麵
莜麵
雲吞麵
+拌麵
+乾拌麵
冷面相
糞穢衊面
僕僕
松山庄
香山庄
中庄子
+新庄子
田庄英雄
本庄
庄司
鬼谷子
谷子敬
洪谷子
-西米谷
-世田谷
聖馬爾谷日
澀谷區
開山闢谷
鬥敗
鬥戰
窩裡鬥
+亂鬥
石樑
木樑
藏歷史
裡面
這裡
中文裡
+英文裡
+古文裡
+經文裡
+論文裡
+譯文裡
+原文裡
+正文裡
+下文裡
+條文裡
+畫裡
洞裡
洞里薩
界裡
村裡的
村裡有
鎮裡
+區裡的
+區裡有
+實驗裡
+註裡
裏白 #植物常用名
烏蘇里 #分詞用
首發
涂醒哲
涂善妮
涂敏恆
+涂爾幹
故云
強制作用
鬱南
卜云吉
黎吉雲
代表
-æ°´ç\84¡æ\80\9cå¥\88
+怜奈
于冠華
于雲鶴
于忠肅集
不太準
非常準
很準
-萬曆
-永曆
囓蟲
勳勞
勳績
煙臺
太醜
御製
-聖人曆
電影後
封為后
皮托管
白面包青天
天神之后
-羅馬曆
-羅馬歷史
-羅馬歷代
-曆數書
-曆局
你誇
誇你
誇我
控制
限制
強制
+改制成
+考試制度
體徵
綜合徵
价川
琺瑯
菜餚
梁啓超
-改制成
王添灯
腌臢
風颳
并力
弔死
弔帶
+繫世
+划上
+划下
+洄遊
+洄游
$overwrite = $this->getOption( 'overwrite', false );
$start = ( $start > 0 )
? $start
- : $dbr->selectField( 'page', 'MIN(page_id)', false, __FUNCTION__ );
+ : $dbr->selectField( 'page', 'MIN(page_id)', false, __METHOD__ );
$end = ( $end > 0 )
? $end
- : $dbr->selectField( 'page', 'MAX(page_id)', false, __FUNCTION__ );
+ : $dbr->selectField( 'page', 'MAX(page_id)', false, __METHOD__ );
if ( !$start ) {
$this->error( "Nothing to do.", true );
}
// Go through each page and save the output
while ( $blockEnd <= $end ) {
// Get the pages
- $res = $dbr->select( 'page', [ 'page_namespace', 'page_title', 'page_id' ],
+ $res = $dbr->select( 'page',
+ [ 'page_namespace', 'page_title', 'page_id' ],
[ 'page_namespace' => MWNamespace::getContentNamespaces(),
"page_id BETWEEN $blockStart AND $blockEnd" ],
+ __METHOD__,
[ 'ORDER BY' => 'page_id ASC', 'USE INDEX' => 'PRIMARY' ]
);
// If the article is cacheable, then load it
if ( $article->isFileCacheable() ) {
- $cache = HTMLFileCache::newFromTitle( $title, 'view' );
+ $cache = new HTMLFileCache( $title, 'view' );
if ( $cache->isCacheGood() ) {
if ( $overwrite ) {
$rebuilt = true;
* @ingroup Maintenance
*/
class RefreshLinks extends Maintenance {
+ /** @var int|bool */
+ protected $namespace = false;
+
public function __construct() {
parent::__construct();
$this->addDescription( 'Refresh link tables' );
$this->addOption( 'e', 'Last page id to refresh', false, true );
$this->addOption( 'dfn-chunk-size', 'Maximum number of existent IDs to check per ' .
'query, default 100000', false, true );
+ $this->addOption( 'namespace', 'Only fix pages in this namespace', false, true );
$this->addArg( 'start', 'Page_id to start from, default 1', false );
$this->setBatchSize( 100 );
}
$start = (int)$this->getArg( 0 ) ?: null;
$end = (int)$this->getOption( 'e' ) ?: null;
$dfnChunkSize = (int)$this->getOption( 'dfn-chunk-size', 100000 );
+ $ns = $this->getOption( 'namespace' );
+ if ( $ns === null ) {
+ $this->namespace = false;
+ } else {
+ $this->namespace = (int)$ns;
+ }
if ( !$this->hasOption( 'dfn-only' ) ) {
$new = $this->getOption( 'new-only', false );
$redir = $this->getOption( 'redirects-only', false );
}
}
+ private function namespaceCond() {
+ return $this->namespace !== false
+ ? [ 'page_namespace' => $this->namespace ]
+ : [];
+ }
+
/**
* Do the actual link refreshing.
* @param int|null $start Page_id to start from
"page_is_redirect=1",
"rd_from IS NULL",
self::intervalCond( $dbr, 'page_id', $start, $end ),
- ];
+ ] + $this->namespaceCond();
$res = $dbr->select(
[ 'page', 'redirect' ],
[
'page_is_new' => 1,
self::intervalCond( $dbr, 'page_id', $start, $end ),
- ],
+ ] + $this->namespaceCond(),
__METHOD__
);
$num = $res->numRows();
if ( $redirectsOnly ) {
$this->fixRedirect( $row->page_id );
} else {
- self::fixLinksFromArticle( $row->page_id );
+ self::fixLinksFromArticle( $row->page_id, $this->namespace );
}
}
} else {
$this->output( "$id\n" );
wfWaitForSlaves();
}
- self::fixLinksFromArticle( $id );
+ self::fixLinksFromArticle( $id, $this->namespace );
}
}
}
$dbw->delete( 'redirect', [ 'rd_from' => $id ],
__METHOD__ );
+ return;
+ } elseif ( $this->namespace !== false
+ && !$page->getTitle()->inNamespace( $this->namespace )
+ ) {
return;
}
/**
* Run LinksUpdate for all links on a given page_id
* @param int $id The page_id
+ * @param int|bool $ns Only fix links if it is in this namespace
*/
- public static function fixLinksFromArticle( $id ) {
+ public static function fixLinksFromArticle( $id, $ns = false ) {
$page = WikiPage::newFromID( $id );
LinkCache::singleton()->clear();
if ( $page === null ) {
return;
+ } elseif ( $ns !== false
+ && !$page->getTitle()->inNamespace( $ns ) ) {
+ return;
}
$content = $page->getContent( Revision::RAW );
$nextStart = $dbr->selectField(
'page',
'page_id',
- self::intervalCond( $dbr, 'page_id', $start, $end ),
+ [ self::intervalCond( $dbr, 'page_id', $start, $end ) ]
+ + $this->namespaceCond(),
__METHOD__,
[ 'ORDER BY' => 'page_id', 'OFFSET' => $chunkSize ]
);
],
'dependencies' => [
'oojs-ui-core',
+ 'oojs-ui-widgets',
'oojs-ui-windows',
'oojs-ui.styles.icons-content',
'oojs-ui.styles.icons-editing-advanced',
// author : Werner Mollentze : https://github.com/wernerm
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('af', {
months : 'Januarie_Februarie_Maart_April_Mei_Junie_Julie_Augustus_September_Oktober_November_Desember'.split('_'),
// author : Abdel Said : https://github.com/abdelsaid
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('ar-ma', {
months : 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'),
// author : Suhail Alkowaileet : https://github.com/xsoh
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var symbolMap = {
'1': '١',
// Native plural forms: forabi https://github.com/forabi
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var symbolMap = {
'1': '١',
// author : topchiyev : https://github.com/topchiyev
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var suffixes = {
1: '-inci',
// Author : Menelion Elensúle : https://github.com/Oire
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
function plural(word, num) {
var forms = word.split('_');
// author : Krasen Borisov : https://github.com/kraz
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('bg', {
months : 'януари_февруари_март_април_май_юни_юли_август_септември_октомври_ноември_декември'.split('_'),
// author : Kaushik Gandhi : https://github.com/kaushikgandhi
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var symbolMap = {
'1': '১',
// author : Thupten N. Chakrishar : https://github.com/vajradog
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var symbolMap = {
'1': '༡',
// author : Jean-Baptiste Le Duigou : https://github.com/jbleduigou
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
function relativeTimeWithMutation(number, withoutSuffix, key) {
var format = {
// based on (hr) translation by Bojan Marković
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
function translate(number, withoutSuffix, key) {
var result = number + ' ';
// author : Juan G. Hurtado : https://github.com/juanghurtado
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('ca', {
months : 'gener_febrer_març_abril_maig_juny_juliol_agost_setembre_octubre_novembre_desembre'.split('_'),
// author : petrbela : https://github.com/petrbela
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var months = 'leden_únor_březen_duben_květen_červen_červenec_srpen_září_říjen_listopad_prosinec'.split('_'),
monthsShort = 'led_úno_bře_dub_kvě_čvn_čvc_srp_zář_říj_lis_pro'.split('_');
// author : Anatoly Mironov : https://github.com/mirontoli
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('cv', {
months : 'кăрлач_нарăс_пуш_ака_май_çĕртме_утă_çурла_авăн_юпа_чӳк_раштав'.split('_'),
// author : Robert Allen
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('cy', {
months: 'Ionawr_Chwefror_Mawrth_Ebrill_Mai_Mehefin_Gorffennaf_Awst_Medi_Hydref_Tachwedd_Rhagfyr'.split('_'),
// author : Ulrik Nielsen : https://github.com/mrbase
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('da', {
months : 'januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december'.split('_'),
// author : Martin Groller : https://github.com/MadMG
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
function processRelativeTime(number, withoutSuffix, key, isFuture) {
var format = {
// author: Menelion Elensúle: https://github.com/Oire
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
function processRelativeTime(number, withoutSuffix, key, isFuture) {
var format = {
// author : Aggelos Karalias : https://github.com/mehiel
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('el', {
monthsNominativeEl : 'Ιανουάριος_Φεβρουάριος_Μάρτιος_Απρίλιος_Μάιος_Ιούνιος_Ιούλιος_Αύγουστος_Σεπτέμβριος_Οκτώβριος_Νοέμβριος_Δεκέμβριος'.split('_'),
// locale : australian english (en-au)
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('en-au', {
months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
// author : Jonathan Abourbih : https://github.com/jonbca
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('en-ca', {
months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
// author : Chris Gedrim : https://github.com/chrisgedrim
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('en-gb', {
months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
// Se ne, bonvolu korekti kaj avizi min por ke mi povas lerni!
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('eo', {
months : 'januaro_februaro_marto_aprilo_majo_junio_julio_aŭgusto_septembro_oktobro_novembro_decembro'.split('_'),
// author : Julio Napurí : https://github.com/julionc
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var monthsShortDot = 'ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.'.split('_'),
monthsShort = 'ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic'.split('_');
// improvements : Illimar Tambek : https://github.com/ragulka
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
function processRelativeTime(number, withoutSuffix, key, isFuture) {
var format = {
// author : Eneko Illarramendi : https://github.com/eillarra
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('eu', {
months : 'urtarrila_otsaila_martxoa_apirila_maiatza_ekaina_uztaila_abuztua_iraila_urria_azaroa_abendua'.split('_'),
// author : Ebrahim Byagowi : https://github.com/ebraminio
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var symbolMap = {
'1': '۱',
// author : Tarmo Aidantausta : https://github.com/bleadof
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var numbersPast = 'nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän'.split(' '),
numbersFuture = [
// author : Ragnar Johannesen : https://github.com/ragnar123
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('fo', {
months : 'januar_februar_mars_apríl_mai_juni_juli_august_september_oktober_november_desember'.split('_'),
// author : Jonathan Abourbih : https://github.com/jonbca
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('fr-ca', {
months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'),
// author : John Fischer : https://github.com/jfroffice
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('fr', {
months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'),
// author : Juan G. Hurtado : https://github.com/juanghurtado
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('gl', {
months : 'Xaneiro_Febreiro_Marzo_Abril_Maio_Xuño_Xullo_Agosto_Setembro_Outubro_Novembro_Decembro'.split('_'),
// author : Tal Ater : https://github.com/TalAter
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('he', {
months : 'ינואר_פברואר_מרץ_אפריל_מאי_יוני_יולי_אוגוסט_ספטמבר_אוקטובר_נובמבר_דצמבר'.split('_'),
// author : Mayank Singhal : https://github.com/mayanksinghal
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var symbolMap = {
'1': '१',
// based on (sl) translation by Robert Sedovšek
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
function translate(number, withoutSuffix, key) {
var result = number + ' ';
// author : Adam Brunner : https://github.com/adambrunner
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var weekEndings = 'vasárnap hétfőn kedden szerdán csütörtökön pénteken szombaton'.split(' ');
// author : Armendarabyan : https://github.com/armendarabyan
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
function monthsCaseReplace(m, format) {
var months = {
// reference: http://id.wikisource.org/wiki/Pedoman_Umum_Ejaan_Bahasa_Indonesia_yang_Disempurnakan
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('id', {
months : 'Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_November_Desember'.split('_'),
// author : Hinrik Örn Sigurðsson : https://github.com/hinrik
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
function plural(n) {
if (n % 100 === 11) {
// author: Mattia Larentis: https://github.com/nostalgiaz
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('it', {
months : 'gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre'.split('_'),
// author : LI Long : https://github.com/baryon
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('ja', {
months : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),
// author : Irakli Janiashvili : https://github.com/irakli-janiashvili
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
function monthsCaseReplace(m, format) {
var months = {
// author : Kruy Vanna : https://github.com/kruyvanna
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('km', {
months: 'មករា_កុម្ភៈ_មិនា_មេសា_ឧសភា_មិថុនា_កក្កដា_សីហា_កញ្ញា_តុលា_វិច្ឆិកា_ធ្នូ'.split('_'),
// - Kyungwook, Park : https://github.com/kyungw00k
// - Jeeeyul Lee <jeeeyul@gmail.com>
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('ko', {
months : '1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월'.split('_'),
// and 'eifelerRegelAppliesToNumber' methods are meant for
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
function processRelativeTime(number, withoutSuffix, key, isFuture) {
var format = {
// author : Mindaugas Mozūras : https://github.com/mmozuras
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var units = {
'm' : 'minutė_minutės_minutę',
// author : Kristaps Karlsons : https://github.com/skakri
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var units = {
'mm': 'minūti_minūtes_minūte_minūtes',
// author : Borislav Mickov : https://github.com/B0k0
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('mk', {
months : 'јануари_февруари_март_април_мај_јуни_јули_август_септември_октомври_ноември_декември'.split('_'),
// author : Floyd Pink : https://github.com/floydpink
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('ml', {
months : 'ജനുവരി_ഫെബ്രുവരി_മാർച്ച്_ഏപ്രിൽ_മേയ്_ജൂൺ_ജൂലൈ_ഓഗസ്റ്റ്_സെപ്റ്റംബർ_ഒക്ടോബർ_നവംബർ_ഡിസംബർ'.split('_'),
// author : Harshad Kale : https://github.com/kalehv
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var symbolMap = {
'1': '१',
// author : Weldan Jamili : https://github.com/weldan
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('ms-my', {
months : 'Januari_Februari_Mac_April_Mei_Jun_Julai_Ogos_September_Oktober_November_Disember'.split('_'),
// author : Squar team, mysquar.com
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var symbolMap = {
'1': '၁',
// Sigurd Gartmann : https://github.com/sigurdga
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('nb', {
months : 'januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember'.split('_'),
// author : suvash : https://github.com/suvash
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var symbolMap = {
'1': '१',
// author : Joris Röling : https://github.com/jjupiter
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var monthsShortWithDots = 'jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.'.split('_'),
monthsShortWithoutDots = 'jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec'.split('_');
// author : https://github.com/mechuwind
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('nn', {
months : 'januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember'.split('_'),
// author : Rafal Hirsz : https://github.com/evoL
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var monthsNominative = 'styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień'.split('_'),
monthsSubjective = 'stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia'.split('_');
// author : Caio Ribeiro Pereira : https://github.com/caio-ribeiro-pereira
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('pt-br', {
months : 'janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro'.split('_'),
// author : Jefferson : https://github.com/jalex79
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('pt', {
months : 'janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro'.split('_'),
// author : Valentin Agachi : https://github.com/avaly
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
function relativeTimeWithPlural(number, withoutSuffix, key) {
var format = {
// Author : Menelion Elensúle : https://github.com/Oire
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
function plural(word, num) {
var forms = word.split('_');
// based on work of petrbela : https://github.com/petrbela
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var months = 'január_február_marec_apríl_máj_jún_júl_august_september_október_november_december'.split('_'),
monthsShort = 'jan_feb_mar_apr_máj_jún_júl_aug_sep_okt_nov_dec'.split('_');
// author : Robert Sedovšek : https://github.com/sedovsek
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
function translate(number, withoutSuffix, key) {
var result = number + ' ';
// author : Oerd Cukalla : https://github.com/oerd (fixes)
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('sq', {
months : 'Janar_Shkurt_Mars_Prill_Maj_Qershor_Korrik_Gusht_Shtator_Tetor_Nëntor_Dhjetor'.split('_'),
// author : Milan Janačković<milanjanackovic@gmail.com> : https://github.com/milan-j
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var translator = {
words: { //Different grammatical cases
// author : Milan Janačković<milanjanackovic@gmail.com> : https://github.com/milan-j
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var translator = {
words: { //Different grammatical cases
// author : Jens Alm : https://github.com/ulmus
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('sv', {
months : 'januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december'.split('_'),
// author : Arjunkumar Krishnamoorthy : https://github.com/tk120404
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
/*var symbolMap = {
'1': '௧',
// author : Kridsada Thanabulpong : https://github.com/sirn
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('th', {
months : 'มกราคม_กุมภาพันธ์_มีนาคม_เมษายน_พฤษภาคม_มิถุนายน_กรกฎาคม_สิงหาคม_กันยายน_ตุลาคม_พฤศจิกายน_ธันวาคม'.split('_'),
// author : Dan Hagman
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('tl-ph', {
months : 'Enero_Pebrero_Marso_Abril_Mayo_Hunyo_Hulyo_Agosto_Setyembre_Oktubre_Nobyembre_Disyembre'.split('_'),
// Burak Yiğit Kaya: https://github.com/BYK
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
var suffixes = {
1: '\'inci',
// author : Abdel Said : https://github.com/abdelsaid
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('tzm-latn', {
months : 'innayr_brˤayrˤ_marˤsˤ_ibrir_mayyw_ywnyw_ywlywz_ɣwšt_šwtanbir_ktˤwbrˤ_nwwanbir_dwjnbir'.split('_'),
// author : Abdel Said : https://github.com/abdelsaid
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('tzm', {
months : 'ⵉⵏⵏⴰⵢⵔ_ⴱⵕⴰⵢⵕ_ⵎⴰⵕⵚ_ⵉⴱⵔⵉⵔ_ⵎⴰⵢⵢⵓ_ⵢⵓⵏⵢⵓ_ⵢⵓⵍⵢⵓⵣ_ⵖⵓⵛⵜ_ⵛⵓⵜⴰⵏⴱⵉⵔ_ⴽⵟⵓⴱⵕ_ⵏⵓⵡⴰⵏⴱⵉⵔ_ⴷⵓⵊⵏⴱⵉⵔ'.split('_'),
// Author : Menelion Elensúle : https://github.com/Oire
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
function plural(word, num) {
var forms = word.split('_');
// author : Sardor Muminov : https://github.com/muminoff
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('uz', {
months : 'январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь'.split('_'),
// author : Bang Nguyen : https://github.com/bangnk
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('vi', {
months : 'tháng 1_tháng 2_tháng 3_tháng 4_tháng 5_tháng 6_tháng 7_tháng 8_tháng 9_tháng 10_tháng 11_tháng 12'.split('_'),
// author : Zeno Zeng : https://github.com/zenozeng
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('zh-cn', {
months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'),
// author : Ben : https://github.com/ben-lin
(function (factory) {
- if (typeof define === 'function' && define.amd) {
+ // Comment out broken wrapper, see T145382
+ /*if (typeof define === 'function' && define.amd) {
define(['moment'], factory); // AMD
} else if (typeof exports === 'object') {
module.exports = factory(require('../moment')); // Node
} else {
factory((typeof global !== 'undefined' ? global : this).moment); // node or other global
- }
+ }*/
+ factory(this.moment);
}(function (moment) {
return moment.defineLocale('zh-tw', {
months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'),
--- /dev/null
+{
+ "@metadata": {
+ "authors": [
+ "SatyamMishra"
+ ]
+ },
+ "ooui-outline-control-move-down": "आइटम नीचे घसकाईं",
+ "ooui-outline-control-move-up": "आइटम ऊपर घसकाईं",
+ "ooui-outline-control-remove": "आइटम हटाईं",
+ "ooui-toolbar-more": "अउरी",
+ "ooui-toolgroup-expand": "अउरी",
+ "ooui-toolgroup-collapse": "कम",
+ "ooui-dialog-message-accept": "ओके",
+ "ooui-dialog-message-reject": "कैंसिल",
+ "ooui-dialog-process-error": "कुछ गड़बड़ी हो गइल",
+ "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": "फाइल इहाँ ड्रॉप करीं"
+}
"ooui-toolgroup-expand": "Liyané",
"ooui-toolgroup-collapse": "Sacukupé",
"ooui-dialog-message-accept": "Ha'a",
- "ooui-dialog-message-reject": "Wurungaké",
+ "ooui-dialog-message-reject": "Wurung",
"ooui-dialog-process-error": "Ana sing klèru",
"ooui-dialog-process-dismiss": "Tutup",
"ooui-dialog-process-retry": "Jajal manèh",
"Vikassy",
"Nayvik",
"Omshivaprakash",
- "Pavanaja"
+ "Pavanaja",
+ "Yogesh"
]
},
"ooui-outline-control-move-down": "ವಸ್ತುವನ್ನು ಕೆಳಗೆ ಸರಿಸು",
"ooui-dialog-process-error": "ಏನೋ ಎಡವಟ್ಟಾಗಿದೆ....",
"ooui-dialog-process-dismiss": "ತೆಗೆದುಹಾಕು",
"ooui-dialog-process-retry": "ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ",
- "ooui-dialog-process-continue": "ಮುಂದುವರೆಸು"
+ "ooui-dialog-process-continue": "ಮುಂದುವರೆಸು",
+ "ooui-selectfile-button-select": "ಕಡತವನ್ನು ಆಯ್ಕೆಮಾಡಿ",
+ "ooui-selectfile-placeholder": "ಕಡತವು ಆಯ್ಕೆಯಾಗಿಲ್ಲ",
+ "ooui-selectfile-dragdrop-placeholder": "ಇಲ್ಲಿ ಕಡತವನ್ನು ಬಿಡಿ"
}
"ooui-toolgroup-collapse": "Mens",
"ooui-dialog-message-accept": "D'acòrdi",
"ooui-dialog-message-reject": "Anullar",
+ "ooui-dialog-process-error": "Quicòm a trucat",
"ooui-dialog-process-dismiss": "Regetar",
"ooui-dialog-process-retry": "Ensajatz tornamai",
"ooui-dialog-process-continue": "Contunhar",
- "ooui-selectfile-placeholder": "Cap de fichièr pas seleccionat"
+ "ooui-selectfile-button-select": "Seleccionar un fichièr",
+ "ooui-selectfile-not-supported": "Lo tipe de fichièr es pas compatible",
+ "ooui-selectfile-placeholder": "Cap de fichièr pas seleccionat",
+ "ooui-selectfile-dragdrop-placeholder": "Depausar lo fichièr aicí"
}
--- /dev/null
+{
+ "@metadata": {
+ "authors": [
+ "Saimawnkham"
+ ]
+ },
+ "ooui-outline-control-move-down": "ၶၢႆႉလူင်းၽၢႆႇတႂ်ႈ",
+ "ooui-outline-control-move-up": "ၶၢႆႉၶိုၼ်ႈၽၢႆႇၼိူဝ်",
+ "ooui-outline-control-remove": "ထွၼ်ပႅတ်ႈ ဢၼ်ၶဝ်ႈပႃး",
+ "ooui-toolbar-more": "ၼမ်ႉလိူဝ်",
+ "ooui-toolgroup-expand": "ၼမ်လိူဝ်",
+ "ooui-toolgroup-collapse": "ဢေႇလိူဝ်",
+ "ooui-dialog-message-accept": "ဢူဝ်ႇၶေႇ",
+ "ooui-dialog-message-reject": "ဢမ်ႇႁဵတ်း",
+ "ooui-dialog-process-error": "သေဢၼ်ဢၼ်ၽိတ်းပိူင်ႈဝႆႉ",
+ "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": "ဢဝ်ၾၢႆႇ သႂ်ႇတီႈၼႆႉ"
+}
"Shanmugamp7",
"Veeven",
"Visdaviva",
- "மதனாஹரன்"
+ "மதனாஹரன்",
+ "రహ్మానుద్దీన్"
]
},
+ "ooui-outline-control-move-down": "అంశాన్ని కిందికి కదుపు",
+ "ooui-outline-control-move-up": "అంశాన్ని పైకి కదుపు",
+ "ooui-outline-control-remove": "అంశాన్ని తీసివేయి",
"ooui-toolbar-more": "మరిన్ని",
"ooui-toolgroup-expand": "మరిన్ని",
"ooui-toolgroup-collapse": "కొన్ని",
"ooui-dialog-process-error": "ఏదో పొరపాటు జరిగింది",
"ooui-dialog-process-dismiss": "రద్దుచేయి",
"ooui-dialog-process-retry": "మళ్ళీ ప్రయత్నించు",
- "ooui-dialog-process-continue": "కొనసాగించు"
+ "ooui-dialog-process-continue": "కొనసాగించు",
+ "ooui-selectfile-button-select": "దస్త్రాన్ని ఎంచుకో",
+ "ooui-selectfile-not-supported": "దస్త్రపు ఎంపిక అందుబాటులో లేదు",
+ "ooui-selectfile-placeholder": "ఏ దస్త్రము ఎంపిక చేయలేదు",
+ "ooui-selectfile-dragdrop-placeholder": "దస్త్రాన్ని ఇక్కడ పడేయండి"
}
--- /dev/null
+{
+ "@metadata": {
+ "authors": [
+ "Muhammad Shuaib"
+ ]
+ },
+ "ooui-outline-control-move-down": "آئیٹم نیچے کھسکائیں",
+ "ooui-outline-control-move-up": "آئیٹم اوپر بڑھائیں",
+ "ooui-outline-control-remove": "آئیٹم حذف کریں",
+ "ooui-toolbar-more": "مزید",
+ "ooui-toolgroup-expand": "مزید",
+ "ooui-toolgroup-collapse": "کم کریں",
+ "ooui-dialog-message-accept": "ٹھیک",
+ "ooui-dialog-message-reject": "منسوخ کریں",
+ "ooui-dialog-process-error": "کچھ غلط ہو گیا ہے",
+ "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.17.8
+ * OOjs UI v0.17.9
* 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-08-16T21:13:48Z
+ * Date: 2016-09-13T18:30:02Z
*/
( function ( OO ) {
/*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
* 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-08-16T21:13:53Z
+ * Date: 2016-09-13T18:30:06Z
*/
.oo-ui-element-hidden {
display: none !important;
/* stylelint-disable-line declaration-no-important */
}
+.oo-ui-buttonElement {
+ display: inline-block;
+ vertical-align: middle;
+}
.oo-ui-buttonElement > .oo-ui-buttonElement-button {
cursor: pointer;
display: inline-block;
text-align: center;
}
.oo-ui-buttonElement > .oo-ui-buttonElement-button {
- color: #333333;
+ color: #333;
}
.oo-ui-buttonElement.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
margin-left: 0;
}
.oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button:hover > .oo-ui-labelElement-label,
.oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button:focus > .oo-ui-labelElement-label {
- color: #000000;
+ color: #000;
}
.oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
- color: #333333;
+ color: #333;
}
.oo-ui-buttonElement-frameless.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
margin-left: 0.25em;
}
.oo-ui-buttonElement-frameless > input.oo-ui-buttonElement-button {
padding-left: 0.25em;
- color: #333333;
+ color: #333;
}
.oo-ui-buttonElement-frameless > input.oo-ui-buttonElement-button:hover,
.oo-ui-buttonElement-frameless > input.oo-ui-buttonElement-button:focus {
- color: #000000;
+ color: #000;
}
.oo-ui-buttonElement-frameless.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
color: #087ecc;
opacity: 0.2;
}
.oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
- color: #cccccc;
+ color: #ccc;
}
.oo-ui-buttonElement-framed > .oo-ui-buttonElement-button {
padding: 0.2em 0.8em;
-webkit-transition: border-color 100ms ease;
-moz-transition: border-color 100ms ease;
transition: border-color 100ms ease;
- background-color: #eeeeee;
- background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #ffffff), color-stop(100%, #dddddd));
- background-image: -webkit-linear-gradient(top, #ffffff 0, #dddddd 100%);
- background-image: -moz-linear-gradient(top, #ffffff 0, #dddddd 100%);
- background-image: linear-gradient(to bottom, #ffffff 0, #dddddd 100%);
- -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffffff', endColorstr='#ffdddddd' )";
+ background-color: #eee;
+ background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #fff), color-stop(100%, #ddd));
+ background-image: -webkit-linear-gradient(top, #fff 0, #ddd 100%);
+ background-image: -moz-linear-gradient(top, #fff 0, #ddd 100%);
+ background-image: linear-gradient(to bottom, #fff 0, #ddd 100%);
+ -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffffffff\', endColorstr=\'#ffdddddd\' )';
}
.oo-ui-buttonElement-framed > .oo-ui-buttonElement-button:hover,
.oo-ui-buttonElement-framed > .oo-ui-buttonElement-button:focus {
- border-color: #aaaaaa;
+ border-color: #aaa;
outline: none;
}
.oo-ui-buttonElement-framed > input.oo-ui-buttonElement-button,
.oo-ui-buttonElement-framed.oo-ui-buttonElement-active > .oo-ui-buttonElement-button,
.oo-ui-buttonElement-framed.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
box-shadow: inset 0 1px 4px 0 rgba(0, 0, 0, 0.07);
- color: #000000;
+ color: #000;
border-color: #c9c9c9;
- background-color: #eeeeee;
- background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #dddddd), color-stop(100%, #ffffff));
- background-image: -webkit-linear-gradient(top, #dddddd 0, #ffffff 100%);
- background-image: -moz-linear-gradient(top, #dddddd 0, #ffffff 100%);
- background-image: linear-gradient(to bottom, #dddddd 0, #ffffff 100%);
- -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffdddddd', endColorstr='#ffffffff' )";
+ background-color: #eee;
+ background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #ddd), color-stop(100%, #fff));
+ background-image: -webkit-linear-gradient(top, #ddd 0, #fff 100%);
+ background-image: -moz-linear-gradient(top, #ddd 0, #fff 100%);
+ background-image: linear-gradient(to bottom, #ddd 0, #fff 100%);
+ -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffdddddd\', endColorstr=\'#ffffffff\' )';
}
.oo-ui-buttonElement-framed.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
margin-left: -0.5em;
background-image: -webkit-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%);
background-image: -moz-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%);
background-image: linear-gradient(to bottom, #eaf4fa 0, #b0d9ee 100%);
- -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffeaf4fa', endColorstr='#ffb0d9ee' )";
+ -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffeaf4fa\', endColorstr=\'#ffb0d9ee\' )';
}
.oo-ui-buttonElement-framed.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:hover,
.oo-ui-buttonElement-framed.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button:focus {
background-image: -webkit-linear-gradient(top, #b0d9ee 0, #eaf4fa 100%);
background-image: -moz-linear-gradient(top, #b0d9ee 0, #eaf4fa 100%);
background-image: linear-gradient(to bottom, #b0d9ee 0, #eaf4fa 100%);
- -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffb0d9ee', endColorstr='#ffeaf4fa' )";
+ -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffb0d9ee\', endColorstr=\'#ffeaf4fa\' )';
}
.oo-ui-buttonElement-framed.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button {
border: 1px solid #b8d892;
background-image: -webkit-linear-gradient(top, #f0fbe1 0, #c3e59a 100%);
background-image: -moz-linear-gradient(top, #f0fbe1 0, #c3e59a 100%);
background-image: linear-gradient(to bottom, #f0fbe1 0, #c3e59a 100%);
- -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#fff0fbe1', endColorstr='#ffc3e59a' )";
+ -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#fff0fbe1\', endColorstr=\'#ffc3e59a\' )';
}
.oo-ui-buttonElement-framed.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button:hover,
.oo-ui-buttonElement-framed.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button:focus {
background-image: -webkit-linear-gradient(top, #c3e59a 0, #f0fbe1 100%);
background-image: -moz-linear-gradient(top, #c3e59a 0, #f0fbe1 100%);
background-image: linear-gradient(to bottom, #c3e59a 0, #f0fbe1 100%);
- -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffc3e59a', endColorstr='#fff0fbe1' )";
+ -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffc3e59a\', endColorstr=\'#fff0fbe1\' )';
}
.oo-ui-buttonElement-framed.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button {
color: #d45353;
opacity: 0.5;
-webkit-transform: translate3d(0, 0, 0);
box-shadow: none;
- color: #333333;
- background: #eeeeee;
- border-color: #cccccc;
+ color: #333;
+ background: #eee;
+ border-color: #ccc;
}
.oo-ui-buttonElement-framed.oo-ui-widget-disabled > .oo-ui-buttonElement-button:hover,
.oo-ui-buttonElement-framed.oo-ui-widget-disabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button:hover,
.oo-ui-buttonElement-framed.oo-ui-widget-disabled > .oo-ui-buttonElement-button:focus,
.oo-ui-buttonElement-framed.oo-ui-widget-disabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button:focus,
.oo-ui-buttonElement-framed.oo-ui-widget-disabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button:focus {
- border-color: #cccccc;
+ border-color: #ccc;
box-shadow: none;
}
.oo-ui-clippableElement-clippable {
}
.oo-ui-fieldLayout:before,
.oo-ui-fieldLayout:after {
- content: " ";
+ content: ' ';
display: table;
}
.oo-ui-fieldLayout:after {
margin-right: 0;
}
.oo-ui-fieldLayout-disabled > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
- color: #cccccc;
+ color: #ccc;
}
.oo-ui-fieldLayout-messages {
list-style: none none;
}
.oo-ui-fieldsetLayout {
position: relative;
+ min-width: 0;
margin: 0;
- padding: 0;
border: 0;
+ padding: 0.01px 0 0 0;
+}
+body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
+ display: table-cell;
}
.oo-ui-fieldsetLayout.oo-ui-iconElement > .oo-ui-iconElement-icon {
display: block;
position: absolute;
}
.oo-ui-fieldsetLayout.oo-ui-labelElement > .oo-ui-labelElement-label {
- display: inline-block;
+ color: inherit;
+ display: table;
+ box-sizing: border-box;
+ max-width: 100%;
+ padding: 0;
+ white-space: normal;
}
.oo-ui-fieldsetLayout > .oo-ui-fieldsetLayout-help {
float: right;
background-color: #a7dcff;
}
.oo-ui-optionWidget.oo-ui-widget-disabled {
- color: #cccccc;
+ color: #ccc;
}
.oo-ui-decoratedOptionWidget {
padding: 0.5em 2em 0.5em 3em;
opacity: 0.2;
}
.oo-ui-buttonWidget {
- display: inline-block;
- vertical-align: middle;
margin-right: 0.5em;
}
.oo-ui-buttonWidget:last-child {
overflow: hidden;
}
.oo-ui-popupWidget-popup {
- background-color: #ffffff;
- border: 1px solid #cccccc;
+ background-color: #fff;
+ border: 1px solid #ccc;
border-radius: 0.25em;
box-shadow: 0 0.15em 0.5em 0 rgba(0, 0, 0, 0.2);
}
}
.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before,
.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
- content: "";
+ content: '';
position: absolute;
width: 0;
height: 0;
.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before {
bottom: -7px;
left: -6px;
- border-bottom-color: #aaaaaa;
+ border-bottom-color: #aaa;
border-width: 7px;
}
.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
bottom: -7px;
left: -5px;
- border-bottom-color: #ffffff;
+ border-bottom-color: #fff;
border-width: 6px;
}
.oo-ui-popupWidget-transitioning .oo-ui-popupWidget-popup {
.oo-ui-inputWidget:last-child {
margin-right: 0;
}
-.oo-ui-buttonInputWidget {
- display: inline-block;
- vertical-align: middle;
-}
.oo-ui-buttonInputWidget > button,
.oo-ui-buttonInputWidget > input {
border: 0;
box-sizing: border-box;
}
.oo-ui-dropdownInputWidget select {
- background-color: #ffffff;
+ background-color: #fff;
height: 2.5em;
padding: 0.5em;
font-size: inherit;
outline: none;
}
.oo-ui-dropdownInputWidget.oo-ui-widget-disabled select {
- color: #cccccc;
- border-color: #dddddd;
+ color: #ccc;
+ border-color: #ddd;
background-color: #f3f3f3;
}
.oo-ui-radioSelectInputWidget .oo-ui-fieldLayout {
overflow: auto;
resize: none;
}
-.oo-ui-textInputWidget [type="number"] {
+.oo-ui-textInputWidget [type='number'] {
-moz-appearance: textfield;
}
-.oo-ui-textInputWidget [type="number"]::-webkit-outer-spin-button,
-.oo-ui-textInputWidget [type="number"]::-webkit-inner-spin-button {
+.oo-ui-textInputWidget [type='number']::-webkit-outer-spin-button,
+.oo-ui-textInputWidget [type='number']::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
-.oo-ui-textInputWidget [type="search"] {
+.oo-ui-textInputWidget [type='search'] {
-webkit-appearance: textfield;
}
-.oo-ui-textInputWidget [type="search"]::-ms-clear {
+.oo-ui-textInputWidget [type='search']::-ms-clear {
display: none;
}
-.oo-ui-textInputWidget [type="search"]::-webkit-search-decoration,
-.oo-ui-textInputWidget [type="search"]::-webkit-search-cancel-button {
+.oo-ui-textInputWidget [type='search']::-webkit-search-decoration,
+.oo-ui-textInputWidget [type='search']::-webkit-search-cancel-button {
display: none;
}
.oo-ui-textInputWidget > .oo-ui-iconElement-icon,
.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-textInputWidget-type-search > .oo-ui-indicatorElement-indicator {
cursor: pointer;
}
+.oo-ui-textInputWidget.oo-ui-widget-disabled input,
+.oo-ui-textInputWidget.oo-ui-widget-disabled textarea {
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-labelElement-label {
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
.oo-ui-textInputWidget.oo-ui-labelElement > .oo-ui-labelElement-label {
display: block;
}
line-height: 1.275em;
font-size: inherit;
font-family: inherit;
- background-color: #ffffff;
- color: #000000;
- border: 1px solid #cccccc;
- box-shadow: 0 0 0 #ffffff, inset 0 0.1em 0.2em #dddddd;
+ background-color: #fff;
+ color: #000;
+ border: 1px solid #ccc;
+ box-shadow: 0 0 0 #fff, inset 0 0.1em 0.2em #ddd;
border-radius: 0.25em;
-webkit-transition: border-color 250ms ease, box-shadow 250ms ease;
-moz-transition: border-color 250ms ease, box-shadow 250ms ease;
.oo-ui-textInputWidget.oo-ui-widget-enabled textarea:focus {
outline: none;
border-color: #a7dcff;
- box-shadow: 0 0 0.3em #a7dcff, 0 0 0 #ffffff;
+ box-shadow: 0 0 0.3em #a7dcff, 0 0 0 #fff;
}
.oo-ui-textInputWidget.oo-ui-widget-enabled input[readonly],
.oo-ui-textInputWidget.oo-ui-widget-enabled textarea[readonly] {
- color: #777777;
+ color: #777;
}
.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid input,
.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid textarea {
- background-color: #ffdddd;
+ background-color: #fdd;
}
.oo-ui-textInputWidget.oo-ui-widget-disabled input,
.oo-ui-textInputWidget.oo-ui-widget-disabled textarea {
- color: #cccccc;
- text-shadow: 0 1px 1px #ffffff;
- border-color: #dddddd;
+ color: #ccc;
+ text-shadow: 0 1px 1px #fff;
+ border-color: #ddd;
background-color: #f3f3f3;
}
.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-iconElement-icon,
opacity: 0.2;
}
.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-labelElement-label {
- color: #dddddd;
- text-shadow: 0 1px 1px #ffffff;
+ color: #ddd;
+ text-shadow: 0 1px 1px #fff;
}
.oo-ui-textInputWidget.oo-ui-iconElement input,
.oo-ui-textInputWidget.oo-ui-iconElement textarea {
.oo-ui-textInputWidget > .oo-ui-labelElement-label {
padding: 0.4em;
line-height: 1.5em;
- color: #888888;
+ color: #888;
}
.oo-ui-textInputWidget-labelPosition-after.oo-ui-indicatorElement > .oo-ui-labelElement-label {
margin-right: 2.0875em;
position: absolute;
width: 100%;
z-index: 4;
- background-color: #ffffff;
+ background-color: #fff;
margin-top: -1px;
- border: 1px solid #cccccc;
+ border: 1px solid #ccc;
border-radius: 0 0 0.25em 0.25em;
box-shadow: 0 0.15em 1em 0 rgba(0, 0, 0, 0.2);
}
.oo-ui-menuOptionWidget .oo-ui-iconElement-icon {
display: none;
}
-.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected {
- background-color: transparent;
-}
.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected .oo-ui-iconElement-icon {
display: block;
}
.oo-ui-menuSectionOptionWidget {
cursor: default;
padding: 0.33em 0.75em;
- color: #888888;
+ color: #888;
}
.oo-ui-dropdownWidget {
display: inline-block;
position: relative;
width: 100%;
max-width: 50em;
- background-color: #ffffff;
+ background-color: #fff;
margin-right: 0.5em;
}
.oo-ui-dropdownWidget-handle {
margin: 0 0.5em;
}
.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle {
- color: #cccccc;
- text-shadow: 0 1px 1px #ffffff;
- border-color: #dddddd;
+ color: #ccc;
+ text-shadow: 0 1px 1px #fff;
+ border-color: #ddd;
background-color: #f3f3f3;
}
.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle:focus {
}
.oo-ui-comboBoxInputWidget {
display: inline-block;
- position: relative;
width: 100%;
max-width: 50em;
margin-right: 0.5em;
line-height: 1.5em;
}
.oo-ui-multioptionWidget.oo-ui-widget-disabled {
- color: #cccccc;
+ color: #ccc;
}
.oo-ui-checkboxMultioptionWidget {
cursor: default;
}
.oo-ui-progressBarWidget {
max-width: 50em;
- background-color: #ffffff;
- border: 1px solid #cccccc;
+ background-color: #fff;
+ border: 1px solid #ccc;
border-radius: 0.25em;
overflow: hidden;
}
.oo-ui-progressBarWidget-bar {
height: 1em;
- border-right: 1px solid #cccccc;
+ border-right: 1px solid #ccc;
-webkit-transition: width 250ms ease, margin-left 250ms ease;
-moz-transition: width 250ms ease, margin-left 250ms ease;
transition: width 250ms ease, margin-left 250ms ease;
background-image: -webkit-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%);
background-image: -moz-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%);
background-image: linear-gradient(to bottom, #eaf4fa 0, #b0d9ee 100%);
- -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffeaf4fa', endColorstr='#ffb0d9ee' )";
+ -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffeaf4fa\', endColorstr=\'#ffb0d9ee\' )';
}
.oo-ui-progressBarWidget-indeterminate .oo-ui-progressBarWidget-bar {
-webkit-animation: oo-ui-progressBarWidget-slide 2s infinite linear;
/*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
* 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-08-16T21:13:53Z
+ * Date: 2016-09-13T18:30:06Z
*/
.oo-ui-element-hidden {
display: none !important;
/* stylelint-disable-line declaration-no-important */
}
+.oo-ui-buttonElement {
+ display: inline-block;
+ vertical-align: middle;
+}
.oo-ui-buttonElement > .oo-ui-buttonElement-button {
cursor: pointer;
display: inline-block;
margin-right: 0.25em;
margin-left: 0.46875em;
}
+.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
+ -webkit-transition: background-color 100ms, color 100ms, border-color 100ms, box-shadow 100ms;
+ -moz-transition: background-color 100ms, color 100ms, border-color 100ms, box-shadow 100ms;
+ transition: background-color 100ms, color 100ms, border-color 100ms, box-shadow 100ms;
+}
+.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon,
+.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
+ opacity: 0.87;
+ -webkit-transition: opacity 100ms;
+ -moz-transition: opacity 100ms;
+ transition: opacity 100ms;
+}
+.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon.oo-ui-image-invert,
+.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator.oo-ui-image-invert {
+ opacity: 1;
+}
+.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-iconElement-icon,
+.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-indicatorElement-indicator {
+ opacity: 0.73;
+}
+.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-iconElement-icon.oo-ui-image-invert,
+.oo-ui-buttonElement.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-indicatorElement-indicator.oo-ui-image-invert {
+ opacity: 1;
+}
+.oo-ui-buttonElement.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon,
+.oo-ui-buttonElement.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
+ opacity: 1;
+}
.oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button .oo-ui-indicatorElement-indicator {
margin-right: 0;
}
padding-left: 0.25em;
padding-right: 0.25em;
}
-.oo-ui-buttonElement-frameless.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
- box-shadow: inset 0 0 0 1px #347bff, 0 0 0 1px #347bff;
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
+ color: #222;
}
-.oo-ui-buttonElement-frameless.oo-ui-widget-enabled > input.oo-ui-buttonElement-button,
-.oo-ui-buttonElement-frameless.oo-ui-widget-enabled > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
- color: #555555;
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
+ color: #444;
+}
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
+ box-shadow: inset 0 0 0 1px #36c, 0 0 0 1px #36c;
}
.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > input.oo-ui-buttonElement-button,
-.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
- color: #444444;
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button:active {
+ color: #000;
}
.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
- color: #347bff;
+ color: #36c;
}
.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-labelElement-label {
- color: #2962cc;
+ color: #447ff5;
}
.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active > .oo-ui-labelElement-label,
.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
- color: #1f4999;
+ color: #2a4b8d;
box-shadow: none;
}
.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
- color: #347bff;
+ color: #36c;
}
.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-labelElement-label {
- color: #2962cc;
+ color: #447ff5;
}
.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active > .oo-ui-labelElement-label,
.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
- color: #1f4999;
+ color: #2a4b8d;
box-shadow: none;
}
.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
- color: #d11d13;
+ color: #c33;
}
.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover > .oo-ui-labelElement-label {
- color: #8c130d;
+ color: #e53939;
}
.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active > .oo-ui-labelElement-label,
.oo-ui-buttonElement-frameless.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
- color: #73100a;
+ color: #873636;
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-frameless.oo-ui-widget-enabled[class*='oo-ui-flaggedElement'] > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
+ opacity: 1;
+}
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled[class*='oo-ui-flaggedElement'] > .oo-ui-buttonElement-button:hover > .oo-ui-iconElement-icon,
+.oo-ui-buttonElement-frameless.oo-ui-widget-enabled[class*='oo-ui-flaggedElement'] > .oo-ui-buttonElement-button:hover > .oo-ui-indicatorElement-indicator {
+ opacity: 0.73;
+}
.oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button {
- color: #cccccc;
+ color: #72777d;
}
.oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon,
.oo-ui-buttonElement-frameless.oo-ui-widget-disabled > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
- opacity: 0.2;
+ opacity: 0.51;
}
.oo-ui-buttonElement-framed.oo-ui-iconElement.oo-ui-labelElement > .oo-ui-buttonElement-button,
.oo-ui-buttonElement-framed.oo-ui-iconElement.oo-ui-indicatorElement > .oo-ui-buttonElement-button {
min-width: 1em;
border-radius: 2px;
position: relative;
- -webkit-transition: background 100ms, color 100ms, border-color 100ms, box-shadow 100ms;
- -moz-transition: background 100ms, color 100ms, border-color 100ms, box-shadow 100ms;
- transition: background 100ms, color 100ms, border-color 100ms, box-shadow 100ms;
}
.oo-ui-buttonElement-framed > input.oo-ui-buttonElement-button,
.oo-ui-buttonElement-framed.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
left: 0.2em;
}
.oo-ui-buttonElement-framed.oo-ui-widget-disabled > .oo-ui-buttonElement-button {
- background-color: #dddddd;
- color: #ffffff;
- border: 1px solid #dddddd;
+ background-color: #c8ccd1;
+ color: #fff;
+ border: 1px solid #c8ccd1;
}
.oo-ui-buttonElement-framed.oo-ui-widget-disabled + .oo-ui-widget-disabled > .oo-ui-buttonElement-button {
- border-left-color: #ffffff;
+ border-left-color: #fff;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button {
- color: #555555;
- background-color: #ffffff;
- border: 1px solid #cccccc;
+ background-color: #f8f9fa;
+ color: #222;
+ border: 1px solid #9aa0a7;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
- background-color: #ebebeb;
+ background-color: #fff;
+ color: #444;
+ border-color: #a2a9b1;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
- border-color: #347bff;
- box-shadow: inset 0 0 0 1px #347bff;
+ border-color: #36c;
+ box-shadow: inset 0 0 0 1px #36c;
}
.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;
- border-color: #d9d9d9;
+ color: #000;
+ border-color: #72777d;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
- background-color: #999999;
- color: #ffffff;
- border-color: #999999;
+ background-color: #2a4b8d;
+ color: #fff;
+ border-color: #2a4b8d;
z-index: 3;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button:focus {
- border-color: #347bff;
+ border-color: #36c;
+ box-shadow: inset 0 0 0 1px #36c, inset 0 0 0 2px #fff;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button {
- color: #347bff;
+ color: #36c;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
- background-color: #ebf2ff;
+ background-color: #fff;
border-color: #859dcc;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
- color: #1f4999;
- border-color: #1f4999;
- box-shadow: none;
-}
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button,
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
- background-color: #999999;
- color: #ffffff;
+ background-color: #eff3fa;
+ color: #2a4b8d;
+ border-color: #2a4b8d;
+ box-shadow: none;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
- border-color: #347bff;
- box-shadow: inset 0 0 0 1px #347bff;
+ border-color: #36c;
+ box-shadow: inset 0 0 0 1px #36c;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button {
- color: #347bff;
+ color: #36c;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
- background-color: #ebf2ff;
+ background-color: #fff;
border-color: #859dcc;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
- color: #1f4999;
- border-color: #1f4999;
- box-shadow: none;
-}
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button,
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
- background-color: #999999;
- color: #ffffff;
+ background-color: #eff3fa;
+ color: #2a4b8d;
+ border-color: #2a4b8d;
+ box-shadow: none;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
- border-color: #347bff;
- box-shadow: inset 0 0 0 1px #347bff;
+ border-color: #36c;
+ 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: #d11d13;
+ color: #c33;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
- background-color: #fbe8e7;
+ background-color: #fff;
border-color: #b77c79;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
- color: #73100a;
- border-color: #73100a;
- box-shadow: none;
-}
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button,
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
- background-color: #999999;
- color: #ffffff;
+ background-color: #fbf4f4;
+ color: #873636;
+ border-color: #873636;
+ box-shadow: none;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
- border-color: #d11d13;
- box-shadow: inset 0 0 0 1px #d11d13;
+ border-color: #c33;
+ box-shadow: inset 0 0 0 1px #c33;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button {
- color: #ffffff;
- background-color: #347bff;
- border-color: #347bff;
+ color: #fff;
+ background-color: #36c;
+ border-color: #36c;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
- background-color: #2962cc;
- border-color: #2962cc;
+ background-color: #447ff5;
+ border-color: #447ff5;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
- color: #ffffff;
- background-color: #1f4999;
- border-color: #1f4999;
- box-shadow: none;
-}
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button,
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
- background-color: #999999;
- color: #ffffff;
+ color: #fff;
+ background-color: #2a4b8d;
+ border-color: #2a4b8d;
+ box-shadow: none;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-progressive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
- border-color: #347bff;
- box-shadow: inset 0 0 0 1px #347bff, inset 0 0 0 2px #ffffff;
+ border-color: #36c;
+ box-shadow: inset 0 0 0 1px #36c, inset 0 0 0 2px #fff;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive > .oo-ui-buttonElement-button {
- color: #ffffff;
- background-color: #347bff;
- border-color: #347bff;
+ color: #fff;
+ background-color: #36c;
+ border-color: #36c;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
- background-color: #2962cc;
- border-color: #2962cc;
+ background-color: #447ff5;
+ border-color: #447ff5;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
- color: #ffffff;
- background-color: #1f4999;
- border-color: #1f4999;
- box-shadow: none;
-}
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button,
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
- background-color: #999999;
- color: #ffffff;
+ color: #fff;
+ background-color: #2a4b8d;
+ border-color: #2a4b8d;
+ box-shadow: none;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-constructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
- border-color: #347bff;
- box-shadow: inset 0 0 0 1px #347bff, inset 0 0 0 2px #ffffff;
+ border-color: #36c;
+ box-shadow: inset 0 0 0 1px #36c, inset 0 0 0 2px #fff;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive > .oo-ui-buttonElement-button {
- color: #ffffff;
- background-color: #d11d13;
- border-color: #d11d13;
+ color: #fff;
+ background-color: #c33;
+ border-color: #c33;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:hover {
- background-color: #8c130d;
- border-color: #8c130d;
+ background-color: #e53939;
+ border-color: #e53939;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
-.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
- color: #ffffff;
- background-color: #73100a;
- border-color: #73100a;
- box-shadow: none;
-}
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled.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-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
- background-color: #999999;
- color: #ffffff;
+ color: #fff;
+ background-color: #873636;
+ border-color: #873636;
+ box-shadow: none;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-primary.oo-ui-flaggedElement-destructive.oo-ui-widget-enabled > .oo-ui-buttonElement-button:focus {
- border-color: #d11d13;
- box-shadow: inset 0 0 0 1px #d11d13, inset 0 0 0 2px #ffffff;
+ border-color: #c33;
+ box-shadow: inset 0 0 0 1px #c33, 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 {
+ opacity: 1;
}
.oo-ui-clippableElement-clippable {
-webkit-box-sizing: border-box;
}
.oo-ui-fieldLayout:before,
.oo-ui-fieldLayout:after {
- content: " ";
+ content: ' ';
display: table;
}
.oo-ui-fieldLayout:after {
margin-right: 0;
}
.oo-ui-fieldLayout-disabled > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
- color: #cccccc;
+ color: #72777d;
}
.oo-ui-fieldLayout-messages {
list-style: none none;
}
.oo-ui-fieldsetLayout {
position: relative;
+ min-width: 0;
margin: 0;
- padding: 0;
border: 0;
+ padding: 0.01px 0 0 0;
+}
+body:not( :-moz-handler-blocked ) .oo-ui-fieldsetLayout {
+ display: table-cell;
}
.oo-ui-fieldsetLayout.oo-ui-iconElement > .oo-ui-iconElement-icon {
display: block;
position: absolute;
}
.oo-ui-fieldsetLayout.oo-ui-labelElement > .oo-ui-labelElement-label {
- display: inline-block;
+ color: inherit;
+ display: table;
+ box-sizing: border-box;
+ max-width: 100%;
+ padding: 0;
+ white-space: normal;
}
.oo-ui-fieldsetLayout > .oo-ui-fieldsetLayout-help {
float: right;
padding: 1.25em;
}
.oo-ui-panelLayout-framed {
- border: 1px solid #aaaaaa;
+ border: 1px solid #a2a9b1;
border-radius: 2px;
box-shadow: 0 0.15em 0 0 rgba(0, 0, 0, 0.15);
}
.oo-ui-optionWidget {
position: relative;
display: block;
- padding: 0.25em 0.5em;
border: 0;
+ padding: 0.25em 0.5em;
}
.oo-ui-optionWidget.oo-ui-widget-enabled {
cursor: pointer;
text-overflow: ellipsis;
overflow: hidden;
}
-.oo-ui-optionWidget-highlighted {
- background-color: #eeeeee;
-}
.oo-ui-optionWidget .oo-ui-labelElement-label {
line-height: 1.5;
}
-.oo-ui-selectWidget-depressed .oo-ui-optionWidget-selected,
-.oo-ui-selectWidget-pressed .oo-ui-optionWidget-pressed,
-.oo-ui-selectWidget-pressed .oo-ui-optionWidget-pressed.oo-ui-optionWidget-highlighted,
-.oo-ui-selectWidget-pressed .oo-ui-optionWidget-pressed.oo-ui-optionWidget-highlighted.oo-ui-optionWidget-selected {
- background-color: #d0d0d0;
+.oo-ui-optionWidget-selected .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
+ opacity: 1;
}
.oo-ui-optionWidget.oo-ui-widget-disabled {
- color: #cccccc;
+ color: #72777d;
}
.oo-ui-decoratedOptionWidget {
padding: 0.5em 2em 0.5em 3em;
}
.oo-ui-decoratedOptionWidget.oo-ui-widget-disabled .oo-ui-iconElement-icon,
.oo-ui-decoratedOptionWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
- opacity: 0.2;
+ opacity: 0.51;
}
.oo-ui-radioSelectWidget:focus {
outline: 0;
}
-.oo-ui-radioSelectWidget:focus .oo-ui-radioOptionWidget.oo-ui-optionWidget-selected .oo-ui-radioInputWidget [type="radio"] + span {
- border-width: 2px;
+.oo-ui-radioSelectWidget:focus [type='radio']:checked + span:before {
+ border-color: #fff;
}
.oo-ui-radioOptionWidget {
cursor: default;
line-height: 2.5;
}
.oo-ui-iconWidget.oo-ui-widget-disabled {
- opacity: 0.2;
+ opacity: 0.51;
}
.oo-ui-indicatorWidget {
display: inline-block;
margin: 0.46875em;
}
.oo-ui-indicatorWidget.oo-ui-widget-disabled {
- opacity: 0.2;
+ opacity: 0.51;
}
.oo-ui-buttonWidget {
- display: inline-block;
- vertical-align: middle;
margin-right: 0.5em;
}
.oo-ui-buttonWidget:last-child {
border-top-right-radius: 2px;
}
.oo-ui-buttonGroupWidget.oo-ui-widget-enabled .oo-ui-buttonElement .oo-ui-buttonElement-button:focus {
- border-color: #347bff;
+ border-color: #36c;
z-index: 3;
}
.oo-ui-popupWidget {
overflow: hidden;
}
.oo-ui-popupWidget-popup {
- background-color: #ffffff;
- border: 1px solid #aaaaaa;
+ background-color: #fff;
+ border: 1px solid #a2a9b1;
border-radius: 2px;
box-shadow: 0 0.15em 0 0 rgba(0, 0, 0, 0.15);
}
}
.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before,
.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
- content: "";
+ content: '';
position: absolute;
width: 0;
height: 0;
.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before {
bottom: -10px;
left: -9px;
- border-bottom-color: #888888;
+ border-bottom-color: #888;
border-width: 10px;
}
.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
bottom: -10px;
left: -8px;
- border-bottom-color: #ffffff;
+ border-bottom-color: #fff;
border-width: 9px;
}
.oo-ui-popupWidget-transitioning .oo-ui-popupWidget-popup {
.oo-ui-inputWidget:last-child {
margin-right: 0;
}
-.oo-ui-buttonInputWidget {
- display: inline-block;
- vertical-align: middle;
-}
.oo-ui-buttonInputWidget > button,
.oo-ui-buttonInputWidget > input {
border: 0;
font: inherit;
vertical-align: middle;
}
-.oo-ui-checkboxInputWidget [type="checkbox"] {
- opacity: 0;
- z-index: 1;
+.oo-ui-checkboxInputWidget [type='checkbox'] {
position: relative;
- cursor: pointer;
- margin: 0;
+ max-width: none;
width: 1.6em;
height: 1.6em;
- max-width: none;
+ margin: 0;
+ opacity: 0;
+ z-index: 1;
}
-.oo-ui-checkboxInputWidget [type="checkbox"] + span {
- -webkit-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
- -moz-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
- transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
+.oo-ui-checkboxInputWidget [type='checkbox'] + span {
+ background-color: #fff;
+ background-origin: border-box;
+ background-position: center center;
+ background-repeat: no-repeat;
+ background-size: 0 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
left: 0;
width: 1.6em;
height: 1.6em;
- background-color: #ffffff;
- background-image: url("themes/mediawiki/images/icons/check-constructive-deprecated.png");
- background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check-constructive-deprecated.svg");
- background-image: linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check-constructive-deprecated.svg");
- background-image: -o-linear-gradient(transparent, transparent), url("themes/mediawiki/images/icons/check-constructive-deprecated.png");
- background-repeat: no-repeat;
- background-position: center center;
- background-origin: border-box;
- background-size: 0 0;
- border: 1px solid #767676;
+ border: 1px solid #72777d;
border-radius: 2px;
}
-.oo-ui-checkboxInputWidget [type="checkbox"]:checked + span {
- background-size: 100% 100%;
+.oo-ui-checkboxInputWidget [type='checkbox']:checked + span {
+ background-image: url('themes/mediawiki/images/icons/check-invert.png');
+ background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url('themes/mediawiki/images/icons/check-invert.svg');
+ background-image: linear-gradient(transparent, transparent), /* @embed */ url('themes/mediawiki/images/icons/check-invert.svg');
+ background-image: -o-linear-gradient(transparent, transparent), url('themes/mediawiki/images/icons/check-invert.png');
+ background-size: 90% 90%;
}
-.oo-ui-checkboxInputWidget [type="checkbox"]:active + span {
- background-color: #767676;
- border-color: #767676;
+.oo-ui-checkboxInputWidget [type='checkbox']:disabled + span {
+ background-color: #c8ccd1;
+ border-color: #c8ccd1;
}
-.oo-ui-checkboxInputWidget [type="checkbox"]:focus + span {
- border-width: 2px;
+.oo-ui-checkboxInputWidget [type='checkbox']:disabled:hover + span {
+ background-color: #c8ccd1;
+ border-color: #c8ccd1;
}
-.oo-ui-checkboxInputWidget [type="checkbox"]:focus:hover + span,
-.oo-ui-checkboxInputWidget [type="checkbox"]:hover + span {
- border-bottom-width: 3px;
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox'] {
+ cursor: pointer;
}
-.oo-ui-checkboxInputWidget [type="checkbox"]:disabled {
- cursor: default;
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox'] + span {
+ cursor: pointer;
+ -webkit-transition: background-color 100ms, background-size 100ms, border-color 100ms, box-shadow 100ms;
+ -moz-transition: background-color 100ms, background-size 100ms, border-color 100ms, box-shadow 100ms;
+ transition: background-color 100ms, background-size 100ms, border-color 100ms, box-shadow 100ms;
+}
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:hover + span,
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:focus:hover + span {
+ border-color: #36c;
+}
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:active + span {
+ background-color: #2a4b8d;
+ border-color: #2a4b8d;
+}
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:focus + span {
+ border-color: #36c;
+ box-shadow: inset 0 0 0 1px #36c;
+}
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:checked + span {
+ background-color: #2a4b8d;
+ border-color: #2a4b8d;
}
-.oo-ui-checkboxInputWidget [type="checkbox"]:disabled + span {
- background-color: #dddddd;
- border-color: #dddddd;
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:checked:hover + span,
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:checked:focus:hover + span {
+ background-color: #36c;
+ border-color: #36c;
}
-.oo-ui-checkboxInputWidget [type="checkbox"]:disabled:checked + span {
- background-image: url("themes/mediawiki/images/icons/check-invert.png");
- background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check-invert.svg");
- background-image: linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check-invert.svg");
- background-image: -o-linear-gradient(transparent, transparent), url("themes/mediawiki/images/icons/check-invert.png");
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:checked:active + span,
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:checked:active:hover + span {
+ background-color: #2a4b8d;
+ border-color: #2a4b8d;
+}
+.oo-ui-checkboxInputWidget.oo-ui-widget-enabled [type='checkbox']:checked:focus + span {
+ background-color: #2a4b8d;
+ border-color: #2a4b8d;
+ box-shadow: inset 0 0 0 1px #36c, inset 0 0 0 2px #fff;
}
.oo-ui-checkboxMultiselectInputWidget .oo-ui-fieldLayout {
margin-bottom: 0;
box-sizing: border-box;
}
.oo-ui-dropdownInputWidget select {
- background-color: #ffffff;
+ background-color: #fff;
height: 2.275em;
font-size: inherit;
font-family: inherit;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
- border: 1px solid #cccccc;
+ border: 1px solid #9aa0a7;
border-radius: 2px;
padding-left: 1em;
vertical-align: middle;
}
.oo-ui-dropdownInputWidget.oo-ui-widget-enabled select:hover,
.oo-ui-dropdownInputWidget.oo-ui-widget-enabled select:focus {
- border-color: #aaaaaa;
+ border-color: #a2a9b1;
outline: 0;
}
.oo-ui-dropdownInputWidget.oo-ui-widget-disabled select {
- color: #cccccc;
- border-color: #dddddd;
- background-color: #f3f3f3;
+ color: #72777d;
+ border-color: #c8ccd1;
+ background-color: #eaecf0;
}
.oo-ui-radioInputWidget {
position: relative;
font: inherit;
vertical-align: middle;
}
-.oo-ui-radioInputWidget [type="radio"] {
- opacity: 0;
- z-index: 1;
+.oo-ui-radioInputWidget [type='radio'] {
position: relative;
- cursor: pointer;
- margin: 0;
+ max-width: none;
width: 1.6em;
height: 1.6em;
- max-width: none;
+ margin: 0;
+ opacity: 0;
+ z-index: 1;
}
-.oo-ui-radioInputWidget [type="radio"] + span {
- -webkit-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
- -moz-transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
- transition: background-size 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
+.oo-ui-radioInputWidget [type='radio'] + span {
+ background-color: #fff;
+ position: absolute;
+ left: 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
- position: absolute;
- left: 0;
width: 1.6em;
height: 1.6em;
- background-color: #ffffff;
- background-image: url("themes/mediawiki/images/icons/circle-constructive-deprecated.png");
- background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/circle-constructive-deprecated.svg");
- background-image: linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/circle-constructive-deprecated.svg");
- background-image: -o-linear-gradient(transparent, transparent), url("themes/mediawiki/images/icons/circle-constructive-deprecated.png");
- background-repeat: no-repeat;
- background-position: center center;
- background-origin: border-box;
- background-size: 0 0;
- border: 1px solid #767676;
+ border: 1px solid #72777d;
border-radius: 100%;
}
-.oo-ui-radioInputWidget [type="radio"]:checked + span {
- background-size: 100% 100%;
+.oo-ui-radioInputWidget [type='radio'] + span:before {
+ content: ' ';
+ position: absolute;
+ top: -4px;
+ left: -4px;
+ right: -4px;
+ bottom: -4px;
+ border: 1px solid transparent;
+ border-radius: 100%;
}
-.oo-ui-radioInputWidget [type="radio"]:active + span {
- background-color: #767676;
- border-color: #767676;
+.oo-ui-radioInputWidget [type='radio']:checked + span {
+ border-width: 0.4em;
}
-.oo-ui-radioInputWidget [type="radio"]:focus + span {
- border-width: 2px;
+.oo-ui-radioInputWidget [type='radio']:checked:hover + span,
+.oo-ui-radioInputWidget [type='radio']:checked:focus:hover + span {
+ border-width: 0.4em;
}
-.oo-ui-radioInputWidget [type="radio"]:focus:hover + span,
-.oo-ui-radioInputWidget [type="radio"]:hover + span {
- border-bottom-width: 3px;
+.oo-ui-radioInputWidget [type='radio']:disabled + span {
+ background-color: #c8ccd1;
+ border-color: #c8ccd1;
}
-.oo-ui-radioInputWidget [type="radio"]:disabled {
- cursor: default;
+.oo-ui-radioInputWidget [type='radio']:disabled:checked + span {
+ background-color: #fff;
+}
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio'] {
+ cursor: pointer;
+}
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio'] + span {
+ cursor: pointer;
+ -webkit-transition: background-color 100ms, border-color 100ms, border-width 100ms;
+ -moz-transition: background-color 100ms, border-color 100ms, border-width 100ms;
+ transition: background-color 100ms, border-color 100ms, border-width 100ms;
+}
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:hover + span {
+ border-color: #36c;
+}
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:active + span {
+ background-color: #2a4b8d;
+ border-color: #2a4b8d;
+}
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:checked + span {
+ border-color: #2a4b8d;
+}
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:checked:hover + span {
+ border-color: #36c;
+}
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:checked:hover:focus + span {
+ border-color: #36c;
+ box-shadow: inset 0 0 0 1px #36c;
+}
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:checked:active + span,
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:checked:active:focus + span {
+ border-color: #2a4b8d;
+ box-shadow: inset 0 0 0 1px #2a4b8d;
}
-.oo-ui-radioInputWidget [type="radio"]:disabled + span {
- background-color: #dddddd;
- border-color: #dddddd;
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:checked:focus + span {
+ box-shadow: inset 0 0 0 1px #36c;
}
-.oo-ui-radioInputWidget [type="radio"]:disabled:checked + span {
- background-image: url("themes/mediawiki/images/icons/circle-invert.png");
- background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/circle-invert.svg");
- background-image: linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/circle-invert.svg");
- background-image: -o-linear-gradient(transparent, transparent), url("themes/mediawiki/images/icons/circle-invert.png");
+.oo-ui-radioInputWidget.oo-ui-widget-enabled [type='radio']:checked:focus + span:before {
+ border-color: #fff;
+ top: -3px;
+ right: -3px;
+ bottom: -3px;
+ left: -3px;
}
.oo-ui-radioSelectInputWidget .oo-ui-fieldLayout {
margin-bottom: 0;
overflow: auto;
resize: none;
}
-.oo-ui-textInputWidget [type="number"] {
+.oo-ui-textInputWidget [type='number'] {
-moz-appearance: textfield;
}
-.oo-ui-textInputWidget [type="number"]::-webkit-outer-spin-button,
-.oo-ui-textInputWidget [type="number"]::-webkit-inner-spin-button {
+.oo-ui-textInputWidget [type='number']::-webkit-outer-spin-button,
+.oo-ui-textInputWidget [type='number']::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
-.oo-ui-textInputWidget [type="search"] {
+.oo-ui-textInputWidget [type='search'] {
-webkit-appearance: textfield;
}
-.oo-ui-textInputWidget [type="search"]::-ms-clear {
+.oo-ui-textInputWidget [type='search']::-ms-clear {
display: none;
}
-.oo-ui-textInputWidget [type="search"]::-webkit-search-decoration,
-.oo-ui-textInputWidget [type="search"]::-webkit-search-cancel-button {
+.oo-ui-textInputWidget [type='search']::-webkit-search-decoration,
+.oo-ui-textInputWidget [type='search']::-webkit-search-cancel-button {
display: none;
}
.oo-ui-textInputWidget > .oo-ui-iconElement-icon,
.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-textInputWidget-type-search > .oo-ui-indicatorElement-indicator {
cursor: pointer;
}
+.oo-ui-textInputWidget.oo-ui-widget-disabled input,
+.oo-ui-textInputWidget.oo-ui-widget-disabled textarea {
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-labelElement-label {
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
.oo-ui-textInputWidget.oo-ui-labelElement > .oo-ui-labelElement-label {
display: block;
}
margin: 0;
font-size: inherit;
font-family: inherit;
- background-color: #ffffff;
- color: #000000;
- border: 1px solid #cccccc;
+ background-color: #fff;
+ color: #000;
+ border: 1px solid #9aa0a7;
border-radius: 2px;
}
.oo-ui-textInputWidget textarea {
}
.oo-ui-textInputWidget.oo-ui-widget-enabled input,
.oo-ui-textInputWidget.oo-ui-widget-enabled textarea {
- box-shadow: inset 0 0 0 0.1em #ffffff;
- -webkit-transition: border 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
- -moz-transition: border 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
- transition: border 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
+ box-shadow: inset 0 0 0 0.1em #fff;
+ -webkit-transition: border-color 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
+ -moz-transition: border-color 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
+ transition: border-color 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
}
.oo-ui-textInputWidget.oo-ui-widget-enabled input:hover,
.oo-ui-textInputWidget.oo-ui-widget-enabled textarea:hover {
- border-color: #aaaaaa;
+ border-color: #72777d;
}
.oo-ui-textInputWidget.oo-ui-widget-enabled input:focus,
.oo-ui-textInputWidget.oo-ui-widget-enabled textarea:focus {
outline: 0;
- border-color: #347bff;
- box-shadow: inset 0 0 0 1px #347bff;
+ border-color: #36c;
+ box-shadow: inset 0 0 0 1px #36c;
}
.oo-ui-textInputWidget.oo-ui-widget-enabled input[readonly],
.oo-ui-textInputWidget.oo-ui-widget-enabled textarea[readonly] {
- color: #777777;
- text-shadow: 0 1px 1px #ffffff;
+ color: #777;
+ text-shadow: 0 1px 1px #fff;
}
.oo-ui-textInputWidget.oo-ui-widget-enabled input[readonly]:hover,
.oo-ui-textInputWidget.oo-ui-widget-enabled textarea[readonly]:hover {
- border-color: #cccccc;
+ border-color: #ccc;
}
.oo-ui-textInputWidget.oo-ui-widget-enabled input[readonly]:focus,
.oo-ui-textInputWidget.oo-ui-widget-enabled textarea[readonly]:focus {
- border-color: #cccccc;
- box-shadow: inset 0 0 0 0.1em #cccccc;
+ border-color: #ccc;
+ box-shadow: inset 0 0 0 0.1em #ccc;
}
.oo-ui-textInputWidget.oo-ui-widget-enabled :-moz-placeholder {
- color: #595959;
+ color: #54595d;
opacity: 1;
}
.oo-ui-textInputWidget.oo-ui-widget-enabled ::-moz-placeholder {
- color: #595959;
+ color: #54595d;
opacity: 1;
}
.oo-ui-textInputWidget.oo-ui-widget-enabled :-ms-input-placeholder {
- color: #595959;
+ color: #54595d;
}
.oo-ui-textInputWidget.oo-ui-widget-enabled ::-webkit-input-placeholder {
- color: #595959;
+ color: #54595d;
}
.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid input,
.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid textarea {
- border-color: #ff0000;
+ border-color: #f00;
}
.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid input:hover,
.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid textarea:hover {
- border-color: #ff0000;
+ border-color: #f00;
}
.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid input:focus,
.oo-ui-textInputWidget.oo-ui-widget-enabled.oo-ui-flaggedElement-invalid textarea:focus {
- border-color: #ff0000;
- box-shadow: inset 0 0 0 0.1em #ff0000;
+ border-color: #f00;
+ box-shadow: inset 0 0 0 0.1em #f00;
}
.oo-ui-textInputWidget.oo-ui-widget-disabled input,
.oo-ui-textInputWidget.oo-ui-widget-disabled textarea {
- color: #cccccc;
- text-shadow: 0 1px 1px #ffffff;
- border-color: #dddddd;
- background-color: #f3f3f3;
+ background-color: #eaecf0;
+ color: #72777d;
+ text-shadow: 0 1px 1px #fff;
+ border-color: #c8ccd1;
}
.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-iconElement-icon,
.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
- opacity: 0.2;
+ opacity: 0.51;
}
.oo-ui-textInputWidget.oo-ui-widget-disabled .oo-ui-labelElement-label {
- color: #cccccc;
- text-shadow: 0 1px 1px #ffffff;
+ color: #72777d;
+ text-shadow: 0 1px 1px #fff;
}
.oo-ui-textInputWidget.oo-ui-iconElement input,
.oo-ui-textInputWidget.oo-ui-iconElement textarea {
.oo-ui-textInputWidget > .oo-ui-labelElement-label {
padding: 0.4em;
line-height: 1.5;
- color: #888888;
+ color: #888;
}
.oo-ui-textInputWidget-labelPosition-after.oo-ui-indicatorElement > .oo-ui-labelElement-label {
margin-right: 2.0875em;
position: absolute;
width: 100%;
z-index: 4;
- background-color: #ffffff;
+ background-color: #fff;
margin-top: -1px;
- border: 1px solid #aaaaaa;
+ border: 1px solid #a2a9b1;
border-radius: 0 0 2px 2px;
box-shadow: 0 0.15em 0 0 rgba(0, 0, 0, 0.15);
}
.oo-ui-menuOptionWidget {
position: relative;
padding: 0.5em 1em;
+ -webkit-transition: background-color 100ms, color 100ms;
+ -moz-transition: background-color 100ms, color 100ms;
+ transition: background-color 100ms, color 100ms;
}
.oo-ui-menuOptionWidget .oo-ui-iconElement-icon {
display: none;
}
-.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected {
- background-color: transparent;
-}
.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected .oo-ui-iconElement-icon {
display: block;
}
+.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted {
+ background-color: #eaecf0;
+ color: #000;
+}
.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected {
- background-color: #d8e6fe;
- color: rgba(0, 0, 0, 0.8);
+ background-color: #eaf3ff;
+ color: #36c;
}
.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected .oo-ui-iconElement-icon {
display: none;
}
-.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted {
- background-color: #eeeeee;
- color: #000000;
-}
-.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted {
- background-color: #d8e6fe;
+.oo-ui-menuOptionWidget.oo-ui-optionWidget-selected.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted,
+.oo-ui-menuOptionWidget.oo-ui-optionWidget-pressed.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted {
+ background-color: rgba(41, 98, 204, 0.1);
+ color: #36c;
}
.oo-ui-menuSectionOptionWidget {
cursor: default;
padding: 0.33em 0.75em;
- color: #888888;
+ color: #888;
}
.oo-ui-dropdownWidget {
display: inline-block;
padding: 0.5em 0;
height: 2.275em;
line-height: 1.275;
- border: 1px solid #cccccc;
+ border: 1px solid #9aa0a7;
border-radius: 2px;
}
.oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator {
margin: 0 1em;
}
.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle {
- background-color: #ffffff;
- -webkit-transition: border-color 100ms;
- -moz-transition: border-color 100ms;
- transition: border-color 100ms;
+ background-color: #f8f9fa;
+ color: #222;
+ -webkit-transition: background-color 100ms, border-color 100ms, box-shadow 100ms;
+ -moz-transition: background-color 100ms, border-color 100ms, box-shadow 100ms;
+ transition: background-color 100ms, border-color 100ms, box-shadow 100ms;
+}
+.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle:hover {
+ background-color: #fff;
+ 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:focus {
+ border-color: #36c;
+ outline: 0;
+ box-shadow: inset 0 0 0 1px #36c;
}
-.oo-ui-dropdownWidget.oo-ui-widget-enabled:hover .oo-ui-dropdownWidget-handle {
- border-color: #aaaaaa;
+.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon,
+.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator {
+ opacity: 0.87;
+ -webkit-transition: opacity 100ms;
+ -moz-transition: opacity 100ms;
+ transition: opacity 100ms;
+}
+.oo-ui-dropdownWidget.oo-ui-widget-enabled.oo-ui-dropdownWidget-open .oo-ui-dropdownWidget-handle {
+ background-color: #fff;
+}
+.oo-ui-dropdownWidget.oo-ui-widget-enabled.oo-ui-dropdownWidget-open .oo-ui-dropdownWidget-handle .oo-ui-iconElement-icon,
+.oo-ui-dropdownWidget.oo-ui-widget-enabled.oo-ui-dropdownWidget-open .oo-ui-dropdownWidget-handle .oo-ui-indicatorElement-indicator {
+ opacity: 1;
}
.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle {
- color: #cccccc;
- text-shadow: 0 1px 1px #ffffff;
- border-color: #dddddd;
- background-color: #f3f3f3;
+ color: #72777d;
+ text-shadow: 0 1px 1px #fff;
+ border-color: #c8ccd1;
+ background-color: #eaecf0;
}
.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle:focus {
outline: 0;
}
.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
- opacity: 0.2;
+ opacity: 0.15;
}
.oo-ui-dropdownWidget.oo-ui-iconElement .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
margin-left: 3em;
}
.oo-ui-comboBoxInputWidget {
display: inline-block;
- position: relative;
- width: 100%;
- max-width: 50em;
- margin-right: 0.5em;
}
.oo-ui-comboBoxInputWidget.oo-ui-widget-enabled > .oo-ui-indicatorElement-indicator {
cursor: pointer;
.oo-ui-comboBoxInputWidget-php > .oo-ui-indicatorElement-indicator {
pointer-events: none;
}
-.oo-ui-comboBoxInputWidget:last-child {
- margin-right: 0;
-}
.oo-ui-comboBoxInputWidget input,
.oo-ui-comboBoxInputWidget textarea {
height: 2.35em;
}
+.oo-ui-comboBoxInputWidget.oo-ui-widget-enabled:hover input,
+.oo-ui-comboBoxInputWidget.oo-ui-widget-enabled:hover textarea {
+ border-color: #a2a9b1;
+}
+.oo-ui-comboBoxInputWidget.oo-ui-widget-enabled:hover input:focus,
+.oo-ui-comboBoxInputWidget.oo-ui-widget-enabled:hover textarea:focus {
+ border-color: #36c;
+}
+.oo-ui-comboBoxInputWidget.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator {
+ opacity: 0.15;
+}
.oo-ui-multioptionWidget {
position: relative;
display: block;
line-height: 1.5;
}
.oo-ui-multioptionWidget.oo-ui-widget-disabled {
- color: #cccccc;
+ color: #72777d;
}
.oo-ui-checkboxMultioptionWidget {
cursor: default;
}
.oo-ui-progressBarWidget {
max-width: 50em;
- background-color: #ffffff;
- border: 1px solid #cccccc;
+ background-color: #fff;
+ border: 1px solid #9aa0a7;
border-radius: 2px;
overflow: hidden;
}
.oo-ui-progressBarWidget-bar {
- background-color: #dddddd;
+ background-color: #ddd;
height: 1em;
-webkit-transition: width 200ms, margin-left 200ms;
-moz-transition: width 200ms, margin-left 200ms;
/*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
* 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-08-16T21:13:48Z
+ * Date: 2016-09-13T18:30:02Z
*/
( function ( OO ) {
// pick up dynamic state, like focus, value of form inputs, scroll position, etc.
state = cls.static.gatherPreInfuseState( $elem[ 0 ], data );
// rebuild widget
- // jscs:disable requireCapitalizedConstructors
obj = new cls( data );
- // jscs:enable requireCapitalizedConstructors
// now replace old DOM with this new DOM.
if ( top ) {
// An efficient constructor might be able to reuse the entire DOM tree of the original element,
* @class
*
* @constructor
- * @param {Object} [config] Configuration options
*/
-OO.ui.Theme = function OoUiTheme( config ) {
- // Configuration initialization
- config = config || {};
-};
+OO.ui.Theme = function OoUiTheme() {};
/* Setup */
// Properties
this.$button = null;
this.framed = null;
- this.active = false;
+ this.active = config.active !== undefined && config.active;
this.onMouseUpHandler = this.onMouseUp.bind( this );
this.onMouseDownHandler = this.onMouseDown.bind( this );
this.onKeyDownHandler = this.onKeyDown.bind( this );
keypress: this.menu.onKeyPressHandler,
blur: this.menu.clearKeyPressBuffer.bind( this.menu )
} );
- this.menu.connect( this, { select: 'onMenuSelect' } );
+ this.menu.connect( this, {
+ select: 'onMenuSelect',
+ toggle: 'onMenuToggle'
+ } );
// Initialization
this.$handle
this.setLabel( selectedLabel );
};
+/**
+ * Handle menu toggle events.
+ *
+ * @private
+ * @param {boolean} isVisible Menu toggle event
+ */
+OO.ui.DropdownWidget.prototype.onMenuToggle = function ( isVisible ) {
+ this.$element.toggleClass( 'oo-ui-dropdownWidget-open', isVisible );
+};
+
/**
* Handle mouse click events.
*
*/
OO.ui.mixin.FloatableElement.prototype.isElementInViewport = function ( $element, $container ) {
var elemRect, contRect,
- topEdgeInBounds = false,
leftEdgeInBounds = false,
bottomEdgeInBounds = false,
rightEdgeInBounds = false;
contRect = $container[ 0 ].getBoundingClientRect();
}
- if ( elemRect.top >= contRect.top && elemRect.top <= contRect.bottom ) {
- topEdgeInBounds = true;
- }
+ // For completeness, if we still cared about topEdgeInBounds, that'd be:
+ // elemRect.top >= contRect.top && elemRect.top <= contRect.bottom
if ( elemRect.left >= contRect.left && elemRect.left <= contRect.right ) {
leftEdgeInBounds = true;
}
// Initialization
this.$element
.addClass( 'oo-ui-fieldLayout' )
+ .toggleClass( 'oo-ui-fieldLayout-disabled', this.fieldWidget.isDisabled() )
.append( this.$help, this.$body );
this.$body.addClass( 'oo-ui-fieldLayout-body' );
this.$messages.addClass( 'oo-ui-fieldLayout-messages' );
this.$field
.addClass( 'oo-ui-fieldLayout-field' )
- .toggleClass( 'oo-ui-fieldLayout-disable', this.fieldWidget.isDisabled() )
.append( this.fieldWidget.$element );
this.setErrors( config.errors || [] );
// Mixin constructors
OO.ui.mixin.IconElement.call( this, config );
- OO.ui.mixin.LabelElement.call( this, config );
+ OO.ui.mixin.LabelElement.call( this, $.extend( {}, config, { $label: $( '<legend>' ) } ) );
OO.ui.mixin.GroupElement.call( this, config );
if ( config.help ) {
// Initialization
this.$element
.addClass( 'oo-ui-fieldsetLayout' )
- .prepend( this.$help, this.$icon, this.$label, this.$group );
+ .prepend( this.$label, this.$help, this.$icon, this.$group );
if ( Array.isArray( config.items ) ) {
this.addItems( config.items );
}
OO.mixinClass( OO.ui.FieldsetLayout, OO.ui.mixin.LabelElement );
OO.mixinClass( OO.ui.FieldsetLayout, OO.ui.mixin.GroupElement );
+/* Static Properties */
+
+OO.ui.FieldsetLayout.static.tagName = 'fieldset';
+
/**
* FormLayouts are used to wrap {@link OO.ui.FieldsetLayout FieldsetLayouts} when you intend to use browser-based
* form submission for the fields instead of handling them in JavaScript. Form layouts can be configured with an
/*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
* 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-08-16T21:13:48Z
+ * Date: 2016-09-13T18:30:02Z
*/
( function ( OO ) {
} else if ( !isFramed && element.isDisabled() ) {
// Frameless disabled button, always use black icon regardless of flags
variants.invert = false;
- } else {
+ } else if ( !element.isDisabled() ) {
// Any other kind of button, use the right colored icon if available
variants.progressive = element.hasFlag( 'progressive' );
variants.constructive = element.hasFlag( 'constructive' );
/*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
* 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-08-16T21:13:53Z
+ * Date: 2016-09-13T18:30:06Z
*/
.oo-ui-popupTool .oo-ui-popupWidget-popup,
.oo-ui-popupTool .oo-ui-popupWidget-anchor {
border-color: rgba(0, 0, 0, 0.1);
}
.oo-ui-toolGroup.oo-ui-widget-enabled .oo-ui-tool-link .oo-ui-tool-title {
- color: #000000;
+ color: #000;
}
.oo-ui-barToolGroup > .oo-ui-iconElement-icon,
.oo-ui-barToolGroup > .oo-ui-labelElement-label {
border-color: rgba(0, 0, 0, 0.2);
box-shadow: inset 0 0.0875em 0.0875em 0 rgba(0, 0, 0, 0.07);
background-color: #f8fbfd;
- background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #f1f7fb), color-stop(100%, #ffffff));
- background-image: -webkit-linear-gradient(top, #f1f7fb 0, #ffffff 100%);
- background-image: -moz-linear-gradient(top, #f1f7fb 0, #ffffff 100%);
- background-image: linear-gradient(to bottom, #f1f7fb 0, #ffffff 100%);
- -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#fff1f7fb', endColorstr='#ffffffff' )";
+ background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #f1f7fb), color-stop(100%, #fff));
+ background-image: -webkit-linear-gradient(top, #f1f7fb 0, #fff 100%);
+ background-image: -moz-linear-gradient(top, #f1f7fb 0, #fff 100%);
+ background-image: linear-gradient(to bottom, #f1f7fb 0, #fff 100%);
+ -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#fff1f7fb\', endColorstr=\'#ffffffff\' )';
}
.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-tool-active.oo-ui-widget-enabled + .oo-ui-tool-active.oo-ui-widget-enabled {
border-left-color: rgba(0, 0, 0, 0.1);
outline: 0;
}
.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-tool-title {
- color: #cccccc;
+ color: #ccc;
}
.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-iconElement-icon {
opacity: 0.2;
outline: 0;
}
.oo-ui-barToolGroup.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-tool-title {
- color: #cccccc;
+ color: #ccc;
}
.oo-ui-barToolGroup.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-iconElement-icon {
opacity: 0.2;
border-bottom-right-radius: 0;
box-shadow: inset 0 0.0875em 0.0875em 0 rgba(0, 0, 0, 0.07);
background-color: #f8fbfd;
- background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #f1f7fb), color-stop(100%, #ffffff));
- background-image: -webkit-linear-gradient(top, #f1f7fb 0, #ffffff 100%);
- background-image: -moz-linear-gradient(top, #f1f7fb 0, #ffffff 100%);
- background-image: linear-gradient(to bottom, #f1f7fb 0, #ffffff 100%);
- -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#fff1f7fb', endColorstr='#ffffffff' )";
+ background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #f1f7fb), color-stop(100%, #fff));
+ background-image: -webkit-linear-gradient(top, #f1f7fb 0, #fff 100%);
+ background-image: -moz-linear-gradient(top, #f1f7fb 0, #fff 100%);
+ background-image: linear-gradient(to bottom, #f1f7fb 0, #fff 100%);
+ -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#fff1f7fb\', endColorstr=\'#ffffffff\' )';
}
.oo-ui-popupToolGroup .oo-ui-toolGroup-tools {
top: 2.5em;
margin: 0 -1px;
- border: 1px solid #cccccc;
- background-color: #ffffff;
+ border: 1px solid #ccc;
+ background-color: #fff;
box-shadow: 0 0.3125em 1.25em rgba(0, 0, 0, 0.25);
}
.oo-ui-popupToolGroup .oo-ui-tool-link {
line-height: 2em;
}
.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel {
- color: #888888;
+ color: #888;
}
.oo-ui-listToolGroup .oo-ui-tool {
display: block;
border-color: rgba(0, 0, 0, 0.1);
box-shadow: inset 0 0.0875em 0.0875em 0 rgba(0, 0, 0, 0.07);
background-color: #f8fbfd;
- background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #f1f7fb), color-stop(100%, #ffffff));
- background-image: -webkit-linear-gradient(top, #f1f7fb 0, #ffffff 100%);
- background-image: -moz-linear-gradient(top, #f1f7fb 0, #ffffff 100%);
- background-image: linear-gradient(to bottom, #f1f7fb 0, #ffffff 100%);
- -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#fff1f7fb', endColorstr='#ffffffff' )";
+ background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #f1f7fb), color-stop(100%, #fff));
+ background-image: -webkit-linear-gradient(top, #f1f7fb 0, #fff 100%);
+ background-image: -moz-linear-gradient(top, #f1f7fb 0, #fff 100%);
+ background-image: linear-gradient(to bottom, #f1f7fb 0, #fff 100%);
+ -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#fff1f7fb\', endColorstr=\'#ffffffff\' )';
}
.oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled + .oo-ui-tool-active.oo-ui-widget-enabled {
border-top-color: rgba(0, 0, 0, 0.1);
opacity: 1;
}
.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-title {
- color: #cccccc;
+ color: #ccc;
}
.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-accel {
- color: #dddddd;
+ color: #ddd;
}
.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-iconElement-icon {
opacity: 0.2;
}
.oo-ui-listToolGroup.oo-ui-widget-disabled {
- color: #cccccc;
+ color: #ccc;
}
.oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator,
.oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon {
background-image: none;
}
.oo-ui-menuToolGroup .oo-ui-tool-active .oo-ui-tool-link .oo-ui-iconElement-icon {
- background-image: url("themes/apex/images/icons/check.png");
- background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/apex/images/icons/check.svg");
- background-image: linear-gradient(transparent, transparent), /* @embed */ url("themes/apex/images/icons/check.svg");
- background-image: -o-linear-gradient(transparent, transparent), url("themes/apex/images/icons/check.png");
+ background-image: url('themes/apex/images/icons/check.png');
+ background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url('themes/apex/images/icons/check.svg');
+ background-image: linear-gradient(transparent, transparent), /* @embed */ url('themes/apex/images/icons/check.svg');
+ background-image: -o-linear-gradient(transparent, transparent), url('themes/apex/images/icons/check.png');
background-size: contain;
background-position: center center;
background-repeat: no-repeat;
background-color: #e1f3ff;
}
.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-title {
- color: #cccccc;
+ color: #ccc;
}
.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-iconElement-icon {
opacity: 0.2;
}
.oo-ui-menuToolGroup.oo-ui-widget-disabled {
- color: #cccccc;
+ color: #ccc;
border-color: rgba(0, 0, 0, 0.05);
}
.oo-ui-menuToolGroup.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator,
line-height: 1;
position: relative;
}
-.oo-ui-toolbar-actions {
- float: right;
-}
-.oo-ui-toolbar-actions .oo-ui-toolbar {
- display: inline-block;
+.oo-ui-toolbar-tools,
+.oo-ui-toolbar-actions,
+.oo-ui-toolbar-shadow {
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
}
.oo-ui-toolbar-tools {
display: inline;
.oo-ui-toolbar-tools .oo-ui-tool {
white-space: normal;
}
-.oo-ui-toolbar-tools,
-.oo-ui-toolbar-actions,
-.oo-ui-toolbar-shadow {
- -webkit-touch-callout: none;
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
+.oo-ui-toolbar-actions {
+ float: right;
+}
+.oo-ui-toolbar-actions .oo-ui-toolbar {
+ display: inline-block;
}
.oo-ui-toolbar-actions .oo-ui-popupWidget {
-webkit-touch-callout: default;
pointer-events: none;
}
.oo-ui-toolbar-bar {
- border-bottom: 1px solid #cccccc;
+ border-bottom: 1px solid #ccc;
background-color: #f8fbfd;
- background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #ffffff), color-stop(100%, #f1f7fb));
- background-image: -webkit-linear-gradient(top, #ffffff 0, #f1f7fb 100%);
- background-image: -moz-linear-gradient(top, #ffffff 0, #f1f7fb 100%);
- background-image: linear-gradient(to bottom, #ffffff 0, #f1f7fb 100%);
- -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffffff', endColorstr='#fff1f7fb' )";
+ background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #fff), color-stop(100%, #f1f7fb));
+ background-image: -webkit-linear-gradient(top, #fff 0, #f1f7fb 100%);
+ background-image: -moz-linear-gradient(top, #fff 0, #f1f7fb 100%);
+ background-image: linear-gradient(to bottom, #fff 0, #f1f7fb 100%);
+ -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffffffff\', endColorstr=\'#fff1f7fb\' )';
}
.oo-ui-toolbar-bar .oo-ui-toolbar-bar {
border: 0;
/*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
* 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-08-16T21:13:53Z
+ * Date: 2016-09-13T18:30:06Z
*/
+.oo-ui-tool.oo-ui-widget-enabled {
+ -webkit-transition: background-color 100ms;
+ -moz-transition: background-color 100ms;
+ transition: background-color 100ms;
+}
+.oo-ui-tool.oo-ui-widget-enabled .oo-ui-tool-link .oo-ui-tool-title {
+ -webkit-transition: color 100ms;
+ -moz-transition: color 100ms;
+ transition: color 100ms;
+}
.oo-ui-popupTool .oo-ui-popupWidget-popup,
.oo-ui-popupTool .oo-ui-popupWidget-anchor {
z-index: 4;
/* @noflip */
margin-left: 1.25em;
}
-.oo-ui-toolGroupTool > .oo-ui-popupToolGroup {
- border: 0;
- border-radius: 0;
- margin: 0;
-}
.oo-ui-toolGroupTool > .oo-ui-toolGroup {
border-right: 0;
}
.oo-ui-toolGroup {
display: inline-block;
vertical-align: middle;
- border-right: 1px solid #cccccc;
- border-radius: 0;
+ border-right: 1px solid #c8ccd1;
}
.oo-ui-toolGroup-empty {
display: none;
outline: 0;
cursor: default;
}
+.oo-ui-toolbar-actions .oo-ui-toolGroup {
+ border-right: 0;
+ border-left: 1px solid #9aa0a7;
+}
.oo-ui-toolbar-narrow .oo-ui-toolGroup + .oo-ui-toolGroup {
margin-left: 0;
}
line-height: 2.1;
padding: 0 0.4em;
}
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-tool-title {
- color: #555555;
-}
.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled:hover {
- background-color: #eeeeee;
+ background-color: #eaecf0;
+}
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled > .oo-ui-tool-link .oo-ui-tool-title {
+ color: #222;
+ -webkit-transition: color 100ms;
+ -moz-transition: color 100ms;
+ transition: color 100ms;
}
.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled.oo-ui-tool-active {
- background-color: #e5e5e5;
+ background-color: #eaf3ff;
box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
}
.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled.oo-ui-tool-active:hover {
- background-color: #eeeeee;
-}
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled.oo-ui-tool-active:active {
- background-color: #e5e5e5;
+ background-color: rgba(41, 98, 204, 0.1);
}
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled > .oo-ui-tool-link .oo-ui-iconElement-icon {
- opacity: 0.7;
-}
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled:hover > .oo-ui-tool-link .oo-ui-iconElement-icon {
- opacity: 0.9;
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled.oo-ui-tool-active > .oo-ui-tool-link .oo-ui-tool-title {
+ color: #36c;
}
.oo-ui-barToolGroup.oo-ui-widget-enabled .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-tool-title,
.oo-ui-barToolGroup.oo-ui-widget-disabled .oo-ui-tool > .oo-ui-tool-link .oo-ui-tool-title {
- color: #cccccc;
+ color: #72777d;
}
.oo-ui-barToolGroup.oo-ui-widget-enabled .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-iconElement-icon,
.oo-ui-barToolGroup.oo-ui-widget-disabled .oo-ui-tool > .oo-ui-tool-link .oo-ui-iconElement-icon {
- opacity: 0.2;
+ opacity: 0.3;
}
.oo-ui-popupToolGroup {
position: relative;
.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-labelElement.oo-ui-indicatorElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
margin-right: 1.75em;
}
+.oo-ui-popupToolGroup-header {
+ line-height: 2.6;
+ margin: 0 0.6em;
+ font-weight: bold;
+}
.oo-ui-popupToolGroup-handle {
padding: 0.3125em;
height: 2.5em;
.oo-ui-toolbar-narrow .oo-ui-popupToolGroup-handle .oo-ui-iconElement-icon {
left: 0;
}
-.oo-ui-popupToolGroup.oo-ui-widget-enabled .oo-ui-popupToolGroup-handle:hover {
- background-color: #eeeeee;
-}
-.oo-ui-popupToolGroup.oo-ui-widget-enabled .oo-ui-popupToolGroup-handle:active {
- background-color: #e5e5e5;
-}
-.oo-ui-popupToolGroup-header {
- line-height: 2.6;
- margin: 0 0.6em;
- font-weight: bold;
-}
-.oo-ui-popupToolGroup-active.oo-ui-widget-enabled {
- border-bottom-left-radius: 0;
- border-bottom-right-radius: 0;
- box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
- background-color: #eeeeee;
-}
.oo-ui-popupToolGroup .oo-ui-toolGroup-tools {
top: 3.125em;
margin: 0 -1px;
- border: 1px solid #cccccc;
- background-color: #ffffff;
+ border: 1px solid #9aa0a7;
+ background-color: #fff;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.2);
min-width: 16em;
}
width: 1.875em;
min-width: 1.875em;
}
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-title {
- padding-left: 0.5em;
- color: #555555;
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-title,
+.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel {
+ line-height: 2;
}
-.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel,
.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-title {
- line-height: 2;
+ padding-left: 0.5em;
+ color: #222;
}
.oo-ui-popupToolGroup .oo-ui-tool-link .oo-ui-tool-accel {
- color: #888888;
+ color: #888;
+}
+.oo-ui-popupToolGroup.oo-ui-widget-enabled {
+ -webkit-transition: background-color 100ms, box-shadow 100ms;
+ -moz-transition: background-color 100ms, box-shadow 100ms;
+ transition: background-color 100ms, box-shadow 100ms;
+}
+.oo-ui-popupToolGroup.oo-ui-widget-enabled.oo-ui-popupToolGroup-active {
+ box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
+ background-color: #eaecf0;
+}
+.oo-ui-popupToolGroup.oo-ui-widget-enabled.oo-ui-popupToolGroup-active .oo-ui-tool-active.oo-ui-widget-enabled .oo-ui-tool-link .oo-ui-tool-title {
+ color: #36c;
+}
+.oo-ui-popupToolGroup.oo-ui-widget-enabled-handle:hover {
+ background-color: #eaecf0;
+}
+.oo-ui-popupToolGroup.oo-ui-widget-enabled-handle:active {
+ background-color: #eaf3ff;
}
.oo-ui-listToolGroup .oo-ui-tool {
display: block;
box-sizing: border-box;
}
.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover {
- background-color: #eeeeee;
+ background-color: #eaecf0;
}
.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover .oo-ui-tool-link .oo-ui-iconElement-icon {
opacity: 0.9;
}
.oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled {
+ background-color: #eaf3ff;
+}
+.oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled:first-child {
box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
- background-color: #e5e5e5;
}
.oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled:hover {
- background-color: #eeeeee;
+ background-color: rgba(41, 98, 204, 0.1);
+}
+.oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled .oo-ui-tool-link .oo-ui-tool-title {
+ color: #36c;
}
.oo-ui-listToolGroup.oo-ui-widget-disabled,
.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-title,
.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-accel {
- color: #cccccc;
+ color: #72777d;
}
.oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator,
.oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon,
.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-iconElement-icon {
- opacity: 0.2;
+ opacity: 0.3;
}
.oo-ui-menuToolGroup .oo-ui-tool {
display: block;
background-image: none;
}
.oo-ui-menuToolGroup .oo-ui-tool-active .oo-ui-tool-link .oo-ui-iconElement-icon {
- background-image: url("themes/mediawiki/images/icons/check.png");
- background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check.svg");
- background-image: linear-gradient(transparent, transparent), /* @embed */ url("themes/mediawiki/images/icons/check.svg");
- background-image: -o-linear-gradient(transparent, transparent), url("themes/mediawiki/images/icons/check.png");
+ background-image: url('themes/mediawiki/images/icons/check-progressive.png');
+ background-image: -webkit-linear-gradient(transparent, transparent), /* @embed */ url('themes/mediawiki/images/icons/check-progressive.svg');
+ background-image: linear-gradient(transparent, transparent), /* @embed */ url('themes/mediawiki/images/icons/check-progressive.svg');
+ background-image: -o-linear-gradient(transparent, transparent), url('themes/mediawiki/images/icons/check-progressive.png');
background-size: contain;
background-position: center center;
background-repeat: no-repeat;
}
.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover {
- background-color: #eeeeee;
+ background-color: rgba(41, 98, 204, 0.1);
+}
+.oo-ui-menuToolGroup .oo-ui-tool-name-menuTool.oo-ui-tool-active {
+ background-color: #eaf3ff;
}
.oo-ui-menuToolGroup.oo-ui-widget-disabled,
.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-title {
- color: #cccccc;
+ color: #72777d;
}
.oo-ui-menuToolGroup.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator,
.oo-ui-menuToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon,
.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-iconElement-icon {
- opacity: 0.2;
+ opacity: 0.3;
}
.oo-ui-toolbar {
clear: both;
line-height: 1;
position: relative;
}
-.oo-ui-toolbar-actions {
- float: right;
-}
-.oo-ui-toolbar-actions .oo-ui-toolbar {
- display: inline-block;
+.oo-ui-toolbar-tools,
+.oo-ui-toolbar-actions,
+.oo-ui-toolbar-shadow {
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
}
.oo-ui-toolbar-tools {
display: inline;
.oo-ui-toolbar-tools .oo-ui-tool {
white-space: normal;
}
-.oo-ui-toolbar-tools,
-.oo-ui-toolbar-actions,
-.oo-ui-toolbar-shadow {
- -webkit-touch-callout: none;
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
+.oo-ui-toolbar-actions {
+ float: right;
+}
+.oo-ui-toolbar-actions .oo-ui-toolbar {
+ display: inline-block;
}
.oo-ui-toolbar-actions .oo-ui-popupWidget {
-webkit-touch-callout: default;
pointer-events: none;
}
.oo-ui-toolbar-bar {
- border-bottom: 1px solid #cccccc;
- background-color: #ffffff;
+ border-bottom: 1px solid #c8ccd1;
+ background-color: #fff;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
font-weight: 500;
- color: #555555;
+ color: #222;
}
.oo-ui-toolbar-bar .oo-ui-toolbar-bar {
- border: 0;
- background: none;
+ border-bottom: 0;
+ background-color: transparent;
box-shadow: none;
}
.oo-ui-toolbar-actions > .oo-ui-buttonElement.oo-ui-labelElement {
.oo-ui-toolbar-actions > .oo-ui-buttonElement.oo-ui-labelElement > .oo-ui-buttonElement-button {
border: 0;
border-radius: 0;
- margin: 0;
padding: 0 0.3125em;
}
.oo-ui-toolbar-actions > .oo-ui-buttonElement.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
margin: 0 1em;
line-height: 3.125em;
}
+.oo-ui-toolbar-actions > .oo-ui-toolbar:not( :last-child ) {
+ border-right: 1px solid #9aa0a7;
+}
/*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
* 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-08-16T21:13:48Z
+ * Date: 2016-09-13T18:30:02Z
*/
( function ( OO ) {
this.active = !!state;
if ( this.active ) {
this.$element.addClass( 'oo-ui-tool-active' );
+ this.setFlags( 'progressive' );
} else {
this.$element.removeClass( 'oo-ui-tool-active' );
+ this.clearFlags();
}
};
/*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
* 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-08-16T21:13:53Z
+ * Date: 2016-09-13T18:30:06Z
*/
.oo-ui-draggableElement-handle,
.oo-ui-draggableElement-handle.oo-ui-widget {
padding: 1.5em;
}
.oo-ui-bookletLayout-outlinePanel {
- border-right: 1px solid #dddddd;
+ border-right: 1px solid #ddd;
}
.oo-ui-bookletLayout-outlinePanel > .oo-ui-outlineControlsWidget {
box-shadow: 0 0 0.25em rgba(0, 0, 0, 0.25);
padding: 0;
background-color: transparent;
}
+.oo-ui-buttonOptionWidget.oo-ui-buttonElement-active .oo-ui-buttonElement-button {
+ cursor: default;
+}
.oo-ui-buttonOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon,
.oo-ui-buttonOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
position: static;
background-color: transparent;
}
.oo-ui-toggleButtonWidget {
- display: inline-block;
- vertical-align: middle;
margin-right: 0.5em;
}
.oo-ui-toggleButtonWidget:last-child {
height: 2em;
width: 4em;
border-radius: 1em;
- box-shadow: 0 0 0 #ffffff, inset 0 0.1em 0.2em #dddddd;
- border: 1px solid #cccccc;
+ box-shadow: 0 0 0 #fff, inset 0 0.1em 0.2em #ddd;
+ border: 1px solid #ccc;
margin-right: 0.5em;
- background-color: #eeeeee;
- background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #dddddd), color-stop(100%, #ffffff));
- background-image: -webkit-linear-gradient(top, #dddddd 0, #ffffff 100%);
- background-image: -moz-linear-gradient(top, #dddddd 0, #ffffff 100%);
- background-image: linear-gradient(to bottom, #dddddd 0, #ffffff 100%);
- -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffdddddd', endColorstr='#ffffffff' )";
+ background-color: #eee;
+ background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #ddd), color-stop(100%, #fff));
+ background-image: -webkit-linear-gradient(top, #ddd 0, #fff 100%);
+ background-image: -moz-linear-gradient(top, #ddd 0, #fff 100%);
+ background-image: linear-gradient(to bottom, #ddd 0, #fff 100%);
+ -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffdddddd\', endColorstr=\'#ffffffff\' )';
}
.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled {
cursor: pointer;
}
.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:hover,
.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:hover .oo-ui-toggleSwitchWidget-grip {
- border-color: #aaaaaa;
+ border-color: #aaa;
}
.oo-ui-toggleSwitchWidget-grip {
top: 0.25em;
-webkit-transition: left 250ms ease, margin-left 250ms ease;
-moz-transition: left 250ms ease, margin-left 250ms ease;
transition: left 250ms ease, margin-left 250ms ease;
- background-color: #eeeeee;
- background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #ffffff), color-stop(100%, #dddddd));
- background-image: -webkit-linear-gradient(top, #ffffff 0, #dddddd 100%);
- background-image: -moz-linear-gradient(top, #ffffff 0, #dddddd 100%);
- background-image: linear-gradient(to bottom, #ffffff 0, #dddddd 100%);
- -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffffff', endColorstr='#ffdddddd' )";
+ background-color: #eee;
+ background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #fff), color-stop(100%, #ddd));
+ background-image: -webkit-linear-gradient(top, #fff 0, #ddd 100%);
+ background-image: -moz-linear-gradient(top, #fff 0, #ddd 100%);
+ background-image: linear-gradient(to bottom, #fff 0, #ddd 100%);
+ -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffffffff\', endColorstr=\'#ffdddddd\' )';
}
.oo-ui-toggleSwitchWidget-glow {
position: absolute;
background-image: -webkit-linear-gradient(top, #b0d9ee 0, #eaf4fa 100%);
background-image: -moz-linear-gradient(top, #b0d9ee 0, #eaf4fa 100%);
background-image: linear-gradient(to bottom, #b0d9ee 0, #eaf4fa 100%);
- -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffb0d9ee', endColorstr='#ffeaf4fa' )";
+ -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffb0d9ee\', endColorstr=\'#ffeaf4fa\' )';
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
}
.oo-ui-selectFileWidget-selectButton {
display: table-cell;
- vertical-align: middle;
}
.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button {
position: relative;
overflow: hidden;
}
-.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button > [type="file"] {
+.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button > [type='file'] {
position: absolute;
top: 0;
bottom: 0;
cursor: pointer;
padding-top: 100px;
}
-.oo-ui-selectFileWidget-selectButton.oo-ui-widget-disabled > .oo-ui-buttonElement-button > [type="file"] {
+.oo-ui-selectFileWidget-selectButton.oo-ui-widget-disabled > .oo-ui-buttonElement-button > [type='file'] {
display: none;
}
.oo-ui-selectFileWidget-info {
right: 0;
text-overflow: ellipsis;
}
-.oo-ui-selectFileWidget-fileType {
- display: none;
-}
.oo-ui-selectFileWidget-clearButton {
position: absolute;
z-index: 2;
}
.oo-ui-selectFileWidget-info {
height: 2.4em;
- background-color: #ffffff;
+ background-color: #fff;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 0.25em 0 0 0.25em;
border-width: 1px 0 1px 1px;
white-space: nowrap;
text-overflow: ellipsis;
}
-.oo-ui-selectFileWidget-fileType {
- color: #888888;
- display: block;
- margin-top: 0.25em;
-}
.oo-ui-selectFileWidget-clearButton {
top: 0;
right: 0;
height: 2.3em;
}
.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-label {
- color: #cccccc;
+ color: #ccc;
}
.oo-ui-selectFileWidget.oo-ui-iconElement .oo-ui-selectFileWidget-label {
left: 2.475em;
background-color: #e1f3ff;
}
.oo-ui-selectFileWidget-dropTarget {
- background-color: #ffffff;
- border: 1px solid #aaaaaa;
+ background-color: #fff;
+ border: 1px solid #aaa;
vertical-align: middle;
border-radius: 0.25em;
}
.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget,
.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget {
background-color: #f3f3f3;
- color: #cccccc;
- border-color: #dddddd;
- text-shadow: 0 1px 1px #ffffff;
+ color: #ccc;
+ border-color: #ddd;
+ text-shadow: 0 1px 1px #fff;
}
.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info,
.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info,
.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info {
background-color: #f3f3f3;
- color: #cccccc;
- border-color: #dddddd;
- text-shadow: 0 1px 1px #ffffff;
+ color: #ccc;
+ border-color: #ddd;
+ text-shadow: 0 1px 1px #fff;
}
.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
opacity: 0.2;
}
.oo-ui-outlineOptionWidget {
- position: relative;
- cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
opacity: 0.5;
}
.oo-ui-outlineOptionWidget.oo-ui-flaggedElement-empty .oo-ui-labelElement-label {
- color: #777777;
+ color: #777;
}
.oo-ui-outlineControlsWidget {
height: 3em;
- background-color: #ffffff;
+ background-color: #fff;
}
.oo-ui-outlineControlsWidget-items,
.oo-ui-outlineControlsWidget-movers {
text-align: left;
white-space: nowrap;
overflow: hidden;
- background-color: #eeeeee;
+ background-color: #eee;
box-shadow: inset 0 -0.015em 0.1em rgba(0, 0, 0, 0.1);
}
.oo-ui-tabOptionWidget {
}
.oo-ui-tabOptionWidget.oo-ui-widget-enabled:hover {
background-color: rgba(255, 255, 255, 0.2);
- border-color: #dddddd;
+ border-color: #ddd;
}
.oo-ui-tabOptionWidget.oo-ui-widget-enabled:active {
- background-color: #ffffff;
- border-color: #dddddd;
+ background-color: #fff;
+ border-color: #ddd;
}
.oo-ui-selectWidget-pressed .oo-ui-tabOptionWidget.oo-ui-optionWidget-selected,
.oo-ui-selectWidget-depressed .oo-ui-tabOptionWidget.oo-ui-optionWidget-selected,
.oo-ui-tabOptionWidget.oo-ui-optionWidget-selected:hover {
- background-color: #ffffff;
- border-color: #dddddd;
+ background-color: #fff;
+ border-color: #ddd;
}
.oo-ui-capsuleMultiselectWidget {
display: inline-block;
display: inline;
}
.oo-ui-capsuleMultiselectWidget-handle {
- background-color: #ffffff;
+ background-color: #fff;
cursor: text;
min-height: 2.4em;
margin-right: 0.5em;
font-size: inherit;
font-family: inherit;
background-color: transparent;
- color: #000000;
+ color: #000;
vertical-align: middle;
}
.oo-ui-capsuleMultiselectWidget-handle > .oo-ui-capsuleMultiselectWidget-content > input:focus {
border-color: rgba(0, 0, 0, 0.2);
}
.oo-ui-capsuleMultiselectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiselectWidget-handle {
- color: #cccccc;
- text-shadow: 0 1px 1px #ffffff;
- border-color: #dddddd;
+ color: #ccc;
+ text-shadow: 0 1px 1px #fff;
+ border-color: #ddd;
background-color: #f3f3f3;
cursor: default;
}
margin: 0.1em;
height: 1.7em;
line-height: 1.7em;
- background-color: #eeeeee;
- background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #ffffff), color-stop(100%, #dddddd));
- background-image: -webkit-linear-gradient(top, #ffffff 0, #dddddd 100%);
- background-image: -moz-linear-gradient(top, #ffffff 0, #dddddd 100%);
- background-image: linear-gradient(to bottom, #ffffff 0, #dddddd 100%);
- -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffffff', endColorstr='#ffdddddd' )";
- border: 1px solid #cccccc;
- color: #555555;
+ background-color: #eee;
+ background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #fff), color-stop(100%, #ddd));
+ background-image: -webkit-linear-gradient(top, #fff 0, #ddd 100%);
+ background-image: -moz-linear-gradient(top, #fff 0, #ddd 100%);
+ background-image: linear-gradient(to bottom, #fff 0, #ddd 100%);
+ -ms-filter: 'progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#ffffffff\', endColorstr=\'#ffdddddd\' )';
+ border: 1px solid #ccc;
+ color: #555;
border-radius: 0.25em;
}
.oo-ui-capsuleItemWidget.oo-ui-labelElement .oo-ui-labelElement-label {
opacity: 0.5;
-webkit-transform: translate3d(0, 0, 0);
box-shadow: none;
- color: #333333;
- background: #eeeeee;
- border-color: #cccccc;
+ color: #333;
+ background: #eee;
+ border-color: #ccc;
}
.oo-ui-capsuleItemWidget > .oo-ui-buttonElement {
margin-top: -1.25em;
position: relative;
max-width: 50em;
}
-.oo-ui-numberInputWidget-field {
- display: table;
- table-layout: fixed;
- width: 100%;
-}
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget,
-.oo-ui-numberInputWidget-field > .oo-ui-textInputWidget {
+.oo-ui-numberInputWidget-buttoned .oo-ui-buttonWidget,
+.oo-ui-numberInputWidget-buttoned .oo-ui-textInputWidget {
display: table-cell;
- vertical-align: middle;
}
-.oo-ui-numberInputWidget-field > .oo-ui-textInputWidget {
- width: 100%;
-}
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget {
- white-space: nowrap;
-}
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget > .oo-ui-buttonElement-button {
+.oo-ui-numberInputWidget-buttoned .oo-ui-buttonElement-button {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
+.oo-ui-numberInputWidget-field {
+ display: table;
+ table-layout: fixed;
+ width: 100%;
+}
.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget {
width: 2.25em;
}
/*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
* 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-08-16T21:13:53Z
+ * Date: 2016-09-13T18:30:06Z
*/
.oo-ui-draggableElement-handle,
.oo-ui-draggableElement-handle.oo-ui-widget {
padding: 1.5em;
}
.oo-ui-bookletLayout-outlinePanel {
- border-right: 1px solid #dddddd;
+ border-right: 1px solid #ddd;
}
.oo-ui-bookletLayout-outlinePanel > .oo-ui-outlineControlsWidget {
box-shadow: 0 0.15em 0 0 rgba(0, 0, 0, 0.15);
border-top-right-radius: 2px;
}
.oo-ui-buttonSelectWidget.oo-ui-widget-enabled:focus .oo-ui-buttonOptionWidget.oo-ui-optionWidget-selected .oo-ui-buttonElement-button {
- border-color: #347bff;
- box-shadow: inset 0 0 0 1px #347bff;
+ border-color: #36c;
+ box-shadow: inset 0 0 0 1px #36c, inset 0 0 0 2px #fff;
}
.oo-ui-buttonOptionWidget {
display: inline-block;
padding: 0;
}
+.oo-ui-buttonOptionWidget.oo-ui-buttonElement-active .oo-ui-buttonElement-button {
+ cursor: default;
+}
.oo-ui-buttonOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon,
.oo-ui-buttonOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
position: static;
background-color: transparent;
}
.oo-ui-toggleButtonWidget {
- display: inline-block;
- vertical-align: middle;
margin-right: 0.5em;
}
.oo-ui-toggleButtonWidget:last-child {
-moz-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
+ background-color: #f8f9fa;
width: 3.5em;
min-height: 26px;
height: 2em;
- border: 1px solid #767676;
+ border: 1px solid #72777d;
border-radius: 1em;
- background-color: #ffffff;
margin-right: 0.5em;
- -webkit-transition: background-color 100ms, border-color 100ms;
- -moz-transition: background-color 100ms, border-color 100ms;
- transition: background-color 100ms, border-color 100ms;
+ -webkit-transition: background-color 250ms, border-color 250ms;
+ -moz-transition: background-color 250ms, border-color 250ms;
+ transition: background-color 250ms, border-color 250ms;
}
.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled {
cursor: pointer;
margin-right: 0;
}
.oo-ui-toggleSwitchWidget:before {
- content: "";
+ content: '';
display: block;
position: absolute;
top: 1px;
border: 1px solid transparent;
border-radius: 1em;
z-index: 1;
- -webkit-transition: border-color 100ms;
- -moz-transition: border-color 100ms;
- transition: border-color 100ms;
+ -webkit-transition: border-color 250ms;
+ -moz-transition: border-color 250ms;
+ transition: border-color 250ms;
}
.oo-ui-toggleSwitchWidget-grip {
top: 0.3125em;
min-height: 16px;
height: 1.25em;
border-radius: 1.25em;
- -webkit-transition: left 100ms, margin-left 100ms;
- -moz-transition: left 100ms, margin-left 100ms;
- transition: left 100ms, margin-left 100ms;
+ -webkit-transition: background-color 250ms, left 100ms, margin-left 100ms;
+ -moz-transition: background-color 250ms, left 100ms, margin-left 100ms;
+ transition: background-color 250ms, left 100ms, margin-left 100ms;
}
.oo-ui-toggleSwitchWidget-glow {
display: none;
margin-left: -2px;
}
.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled .oo-ui-toggleSwitchWidget-grip {
- border: 1px solid #767676;
+ background-color: #f8f9fa;
+ border: 1px solid #72777d;
}
.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:hover {
- border-color: #2962cc;
+ background-color: #fff;
+ border-color: #447ff5;
}
.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:hover .oo-ui-toggleSwitchWidget-grip {
- border-color: #2962cc;
+ background-color: #fff;
+ border-color: #447ff5;
}
.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:active,
.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:active:hover {
- background-color: #767676;
- border-color: #347bff;
+ background-color: #36c;
+ border-color: #2a4b8d;
}
.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:active .oo-ui-toggleSwitchWidget-grip,
.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:active:hover .oo-ui-toggleSwitchWidget-grip {
- background-color: #ffffff;
- border-color: #ffffff;
+ background-color: #fff;
+ border-color: #fff;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
}
.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:focus {
- border-color: #347bff;
- box-shadow: inset 0 0 0 1px #347bff;
+ border-color: #36c;
+ box-shadow: inset 0 0 0 1px #36c;
outline: 0;
}
.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled:focus .oo-ui-toggleSwitchWidget-grip {
- border-color: #347bff;
+ border-color: #36c;
}
.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled.oo-ui-toggleWidget-on {
- background-color: #347bff;
- border-color: #347bff;
+ background-color: #2a4b8d;
+ border-color: #2a4b8d;
}
.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled.oo-ui-toggleWidget-on .oo-ui-toggleSwitchWidget-grip {
- background-color: #ffffff;
- border-color: #ffffff;
+ background-color: #fff;
+ border-color: #fff;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
}
.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled.oo-ui-toggleWidget-on:hover {
- background-color: #2962cc;
- border-color: #2962cc;
+ background-color: #36c;
+ border-color: #36c;
}
.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled.oo-ui-toggleWidget-on:active,
.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled.oo-ui-toggleWidget-on:active:hover {
- background-color: #1f4999;
- border-color: #1f4999;
+ background-color: #2a4b8d;
+ border-color: #2a4b8d;
}
.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled.oo-ui-toggleWidget-on:focus {
- border-color: #347bff;
+ border-color: #36c;
}
.oo-ui-toggleSwitchWidget.oo-ui-widget-enabled.oo-ui-toggleWidget-on:focus:before {
- border-color: #ffffff;
+ border-color: #fff;
}
.oo-ui-toggleSwitchWidget.oo-ui-widget-disabled {
- background-color: #dddddd;
- border-color: #dddddd;
+ background-color: #c8ccd1;
+ border-color: #c8ccd1;
outline: 0;
}
-.oo-ui-toggleSwitchWidget.oo-ui-widget-disabled .oo-ui-toggleSwitchWidget-grip {
- background-color: #ffffff;
+.oo-ui-toggleSwitchWidget.oo-ui-widget-disabled.oo-ui-toggleWidget-off .oo-ui-toggleSwitchWidget-grip {
+ border: 1px solid #fff;
+ box-shadow: inset 0 0 0 1px #fff;
+}
+.oo-ui-toggleSwitchWidget.oo-ui-widget-disabled.oo-ui-toggleWidget-on .oo-ui-toggleSwitchWidget-grip {
+ background-color: #fff;
}
.oo-ui-selectFileWidget {
display: inline-block;
}
.oo-ui-selectFileWidget-selectButton {
display: table-cell;
- vertical-align: middle;
}
.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button {
position: relative;
overflow: hidden;
}
-.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button > [type="file"] {
+.oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button > [type='file'] {
position: absolute;
top: 0;
bottom: 0;
cursor: pointer;
padding-top: 100px;
}
-.oo-ui-selectFileWidget-selectButton.oo-ui-widget-disabled > .oo-ui-buttonElement-button > [type="file"] {
+.oo-ui-selectFileWidget-selectButton.oo-ui-widget-disabled > .oo-ui-buttonElement-button > [type='file'] {
display: none;
}
.oo-ui-selectFileWidget-info {
right: 0;
text-overflow: ellipsis;
}
-.oo-ui-selectFileWidget-fileType {
- display: none;
-}
.oo-ui-selectFileWidget-clearButton {
position: absolute;
z-index: 2;
}
.oo-ui-selectFileWidget-info {
height: 2.4em;
- background-color: #ffffff;
- border: 1px solid #cccccc;
+ background-color: #fff;
+ border: 1px solid #9aa0a7;
border-radius: 2px 0 0 2px;
border-width: 1px 0 1px 1px;
}
text-overflow: ellipsis;
padding-left: 0.5em;
}
-.oo-ui-selectFileWidget-fileType {
- color: #888888;
- display: block;
- margin-top: 0.25em;
-}
.oo-ui-selectFileWidget-clearButton {
top: 0;
right: 0;
height: 2.3em;
}
.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-label {
- color: #cccccc;
+ color: #72777d;
}
.oo-ui-selectFileWidget.oo-ui-iconElement .oo-ui-selectFileWidget-label {
left: 2.875em;
right: 2em;
}
.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled.oo-ui-selectFileWidget-canDrop.oo-ui-selectFileWidget-dropTarget {
- background-color: #ebf2ff;
+ background-color: #eaf3ff;
}
.oo-ui-selectFileWidget-dropTarget {
- background-color: #ffffff;
- border: 1px solid #cccccc;
+ background-color: #fff;
+ border: 1px solid #9aa0a7;
vertical-align: middle;
overflow: hidden;
border-radius: 2px;
overflow: inherit;
white-space: normal;
}
-.oo-ui-selectFileWidget-empty.oo-ui-selectFileWidget-dropTarget {
- background-color: #eeeeee;
+.oo-ui-selectFileWidget-empty.oo-ui-widget-enabled.oo-ui-selectFileWidget-dropTarget {
+ background-color: #eee;
border-style: dashed;
}
.oo-ui-selectFileWidget.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget,
.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget,
.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget {
- background-color: #f3f3f3;
- border-color: #dddddd;
+ background-color: #eaecf0;
+ border-color: #c8ccd1;
}
.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info,
.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info,
.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info {
- background-color: #f3f3f3;
- color: #cccccc;
- border-color: #dddddd;
- text-shadow: 0 1px 1px #ffffff;
+ background-color: #eaecf0;
+ color: #72777d;
+ border-color: #c8ccd1;
+ text-shadow: 0 1px 1px #fff;
}
.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
- opacity: 0.2;
+ opacity: 0.51;
}
.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropLabel {
display: none;
}
.oo-ui-outlineOptionWidget {
- position: relative;
- cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
font-size: 1.1em;
padding: 0.75em;
+ -webkit-transition: background-color 100ms, color 100ms;
+ -moz-transition: background-color 100ms, color 100ms;
+ transition: background-color 100ms, color 100ms;
+}
+.oo-ui-outlineOptionWidget.oo-ui-optionWidget-highlighted {
+ background-color: #eaecf0;
+ color: #000;
+}
+.oo-ui-outlineOptionWidget.oo-ui-optionWidget-selected {
+ background-color: #eaf3ff;
+ color: #36c;
+}
+.oo-ui-outlineOptionWidget.oo-ui-optionWidget-pressed {
+ background-color: rgba(41, 98, 204, 0.1);
+ color: #36c;
}
.oo-ui-outlineOptionWidget .oo-ui-iconElement-icon {
font-size: 90.90909%;
.oo-ui-outlineOptionWidget-level-2 .oo-ui-iconElement-icon {
left: 4em;
}
-.oo-ui-selectWidget-depressed .oo-ui-outlineOptionWidget.oo-ui-optionWidget-selected {
- background-color: #d0d0d0;
- text-shadow: 0 1px 1px #ffffff;
-}
.oo-ui-outlineOptionWidget.oo-ui-flaggedElement-important {
font-weight: bold;
}
opacity: 0.5;
}
.oo-ui-outlineOptionWidget.oo-ui-flaggedElement-empty .oo-ui-labelElement-label {
- color: #777777;
+ color: #777;
}
.oo-ui-outlineControlsWidget {
height: 3em;
- background-color: #ffffff;
+ background-color: #fff;
}
.oo-ui-outlineControlsWidget-items,
.oo-ui-outlineControlsWidget-movers {
text-align: left;
white-space: nowrap;
overflow: hidden;
- background-color: #dddddd;
+ background-color: #ddd;
}
.oo-ui-tabOptionWidget {
display: inline-block;
border-bottom: 0;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
- color: #555555;
+ color: #222;
font-weight: bold;
}
.oo-ui-tabOptionWidget.oo-ui-widget-enabled:hover {
.oo-ui-selectWidget-pressed .oo-ui-tabOptionWidget.oo-ui-optionWidget-selected,
.oo-ui-selectWidget-depressed .oo-ui-tabOptionWidget.oo-ui-optionWidget-selected,
.oo-ui-tabOptionWidget.oo-ui-optionWidget-selected:hover {
- background-color: #ffffff;
- color: #333333;
+ background-color: #fff;
+ color: #333;
}
.oo-ui-capsuleMultiselectWidget {
display: inline-block;
min-height: 2.4em;
margin-right: 0.5em;
padding: 0.15em 0.25em;
- border: 1px solid #cccccc;
+ border: 1px solid #9aa0a7;
border-radius: 2px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
.oo-ui-capsuleMultiselectWidget-handle:last-child {
margin-right: 0;
}
+.oo-ui-capsuleMultiselectWidget-handle:hover {
+ border-color: #72777d;
+}
.oo-ui-capsuleMultiselectWidget-handle > .oo-ui-indicatorElement-indicator,
.oo-ui-capsuleMultiselectWidget-handle > .oo-ui-iconElement-icon {
position: absolute;
font-size: inherit;
font-family: inherit;
background-color: transparent;
- color: #000000;
+ color: #000;
vertical-align: middle;
}
.oo-ui-capsuleMultiselectWidget-handle > .oo-ui-capsuleMultiselectWidget-content > input:focus {
top: 0;
margin: 0.3em;
}
+.oo-ui-capsuleMultiselectWidget .oo-ui-popupWidget {
+ width: 100%;
+ margin-top: -1px;
+}
+.oo-ui-capsuleMultiselectWidget .oo-ui-popupWidget-popup {
+ min-width: 100%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ border-width: 0 1px;
+ border-radius: 0 0 2px 2px;
+}
.oo-ui-capsuleMultiselectWidget.oo-ui-widget-enabled .oo-ui-capsuleMultiselectWidget-handle {
- background-color: #ffffff;
+ background-color: #fff;
cursor: text;
- -webkit-transition: border-color 100ms;
- -moz-transition: border-color 100ms;
- transition: border-color 100ms;
+ -webkit-transition: border-color 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
+ -moz-transition: border-color 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
+ transition: border-color 200ms cubic-bezier(0.39, 0.575, 0.565, 1), box-shadow 200ms cubic-bezier(0.39, 0.575, 0.565, 1);
}
.oo-ui-capsuleMultiselectWidget.oo-ui-widget-enabled:hover .oo-ui-capsuleMultiselectWidget-handle {
- border-color: #aaaaaa;
+ border-color: #a2a9b1;
+}
+.oo-ui-capsuleMultiselectWidget.oo-ui-widget-enabled.oo-ui-capsuleMultiselectWidget-open .oo-ui-capsuleMultiselectWidget-handle {
+ border-color: #36c;
+ outline: 0;
+ box-shadow: inset 0 0 0 1px #36c;
}
.oo-ui-capsuleMultiselectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiselectWidget-handle {
- color: #cccccc;
- text-shadow: 0 1px 1px #ffffff;
- border-color: #dddddd;
- background-color: #f3f3f3;
+ color: #72777d;
+ text-shadow: 0 1px 1px #fff;
+ border-color: #c8ccd1;
+ background-color: #eaecf0;
+}
+.oo-ui-capsuleMultiselectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiselectWidget-handle > .oo-ui-iconElement-icon {
+ opacity: 0.51;
}
-.oo-ui-capsuleMultiselectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiselectWidget-handle > .oo-ui-iconElement-icon,
.oo-ui-capsuleMultiselectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiselectWidget-handle > .oo-ui-indicatorElement-indicator {
- opacity: 0.2;
+ opacity: 0.15;
}
.oo-ui-capsuleItemWidget {
position: relative;
vertical-align: middle;
height: 1.7em;
line-height: 1.7;
- background-color: #eeeeee;
- color: #555555;
+ background-color: #eee;
+ color: #222;
margin: 0.1em;
- border: 1px solid #cccccc;
+ border: 1px solid #9aa0a7;
border-radius: 2px;
padding: 0 0.4em;
}
}
.oo-ui-capsuleItemWidget:focus {
outline: 0;
- border-color: #347bff;
- box-shadow: inset 0 0 0 1px #347bff;
+ border-color: #36c;
+ box-shadow: inset 0 0 0 1px #36c;
}
.oo-ui-capsuleItemWidget.oo-ui-widget-disabled {
- background-color: #f3f3f3;
- color: #cccccc;
- border-color: #dddddd;
- text-shadow: 0 1px 1px #ffffff;
+ background-color: #eaecf0;
+ color: #72777d;
+ border-color: #c8ccd1;
+ text-shadow: 0 1px 1px #fff;
}
.oo-ui-capsuleItemWidget > .oo-ui-buttonElement {
display: none;
.oo-ui-searchWidget-query {
height: 4em;
padding: 0 1em;
- border-bottom: 1px solid #cccccc;
+ border-bottom: 1px solid #9aa0a7;
}
.oo-ui-searchWidget-query .oo-ui-textInputWidget {
margin: 0.75em 0;
position: relative;
max-width: 50em;
}
-.oo-ui-numberInputWidget-field {
- display: table;
- table-layout: fixed;
- width: 100%;
-}
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget,
-.oo-ui-numberInputWidget-field > .oo-ui-textInputWidget {
+.oo-ui-numberInputWidget-buttoned .oo-ui-buttonWidget,
+.oo-ui-numberInputWidget-buttoned .oo-ui-textInputWidget {
display: table-cell;
- vertical-align: middle;
-}
-.oo-ui-numberInputWidget-field > .oo-ui-textInputWidget {
- width: 100%;
}
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget {
- white-space: nowrap;
-}
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget > .oo-ui-buttonElement-button {
+.oo-ui-numberInputWidget-buttoned .oo-ui-buttonElement-button {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
-.oo-ui-numberInputWidget-field > .oo-ui-buttonWidget {
+.oo-ui-numberInputWidget-field {
+ display: table;
+ table-layout: fixed;
+ width: 100%;
+}
+.oo-ui-numberInputWidget-buttoned .oo-ui-buttonWidget {
width: 2.5em;
}
+.oo-ui-numberInputWidget-buttoned .oo-ui-buttonElement-button {
+ display: block;
+ padding-left: 0;
+ padding-right: 0;
+}
+.oo-ui-numberInputWidget-buttoned .oo-ui-textInputWidget input {
+ border-radius: 0;
+}
.oo-ui-numberInputWidget-minusButton.oo-ui-buttonElement-framed > .oo-ui-buttonElement-button {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
border-left-width: 0;
}
-.oo-ui-numberInputWidget-buttoned .oo-ui-textInputWidget input {
- border-radius: 0;
-}
/*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
* 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-08-16T21:13:48Z
+ * Date: 2016-09-13T18:30:02Z
*/
( function ( OO ) {
* };
*
* var card1 = new CardOneLayout( 'one' ),
- * card2 = new CardLayout( 'two', { label: 'Card two' } );
+ * card2 = new OO.ui.CardLayout( 'two', { label: 'Card two' } );
*
* card2.$element.append( '<p>Second card</p>' );
*
OO.ui.ToggleButtonWidget.parent.call( this, config );
// Mixin constructors
- OO.ui.mixin.ButtonElement.call( this, config );
+ OO.ui.mixin.ButtonElement.call( this, $.extend( {}, config, { active: this.active } ) );
OO.ui.mixin.IconElement.call( this, config );
OO.ui.mixin.IndicatorElement.call( this, config );
OO.ui.mixin.LabelElement.call( this, config );
/* Static Properties */
-OO.ui.OutlineOptionWidget.static.highlightable = false;
+OO.ui.OutlineOptionWidget.static.highlightable = true;
OO.ui.OutlineOptionWidget.static.scrollIntoViewOnSelect = true;
return this.level;
};
+/**
+ * @inheritdoc
+ */
+OO.ui.OutlineOptionWidget.prototype.setPressed = function ( state ) {
+ OO.ui.OutlineOptionWidget.parent.prototype.setPressed.call( this, state );
+ if ( this.constructor.static.pressable ) {
+ this.pressed = !!state;
+ if ( this.pressed ) {
+ this.setFlags( 'progressive' );
+ } else if ( !this.selected ) {
+ this.clearFlags();
+ }
+ }
+ return this;
+};
+
/**
* Set movability.
*
return this;
};
+/**
+ * @inheritdoc
+ */
+OO.ui.OutlineOptionWidget.prototype.setSelected = function ( state ) {
+ OO.ui.OutlineOptionWidget.parent.prototype.setSelected.call( this, state );
+ if ( this.constructor.static.selectable ) {
+ this.selected = !!state;
+ if ( this.selected ) {
+ this.setFlags( 'progressive' );
+ } else {
+ this.clearFlags();
+ }
+ }
+ return this;
+};
+
/**
* Set indentation level.
*
}
this.menu.connect( this, {
choose: 'onMenuChoose',
+ toggle: 'onMenuToggle',
add: 'onMenuItemsChange',
remove: 'onMenuItemsChange'
} );
}
};
+/**
+ * Handle menu toggle events.
+ *
+ * @private
+ * @param {boolean} isVisible Menu toggle event
+ */
+OO.ui.CapsuleMultiselectWidget.prototype.onMenuToggle = function ( isVisible ) {
+ this.$element.toggleClass( 'oo-ui-capsuleMultiselectWidget-open', isVisible );
+};
+
/**
* Handle menu item change events.
*
.addClass( 'oo-ui-selectFileWidget-fileName' )
.text( this.currentFile.name )
);
- if ( this.currentFile.type !== '' ) {
- $label = $label.add(
- $( '<span>' )
- .addClass( 'oo-ui-selectFileWidget-fileType' )
- .text( this.currentFile.type )
- );
- }
this.setLabel( $label );
if ( this.showDropTarget ) {
return false;
}
- /*jshint bitwise: false */
+ /* eslint-disable no-bitwise */
if ( this.isInteger && ( n | 0 ) !== n ) {
return false;
}
- /*jshint bitwise: true */
+ /* eslint-enable no-bitwise */
if ( n < this.min || n > this.max ) {
return false;
/*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
* 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-08-16T21:13:53Z
+ * Date: 2016-09-13T18:30:06Z
*/
.oo-ui-actionWidget.oo-ui-pendingElement-pending {
background-image: /* @embed */ url(themes/apex/images/textures/pending.gif);
.oo-ui-messageDialog-title {
font-size: 1.5em;
line-height: 1em;
- color: #000000;
+ color: #000;
}
.oo-ui-messageDialog-message {
font-size: 0.9em;
line-height: 1.25em;
- color: #666666;
+ color: #666;
}
.oo-ui-messageDialog-message-verbose {
font-size: 1.1em;
}
.oo-ui-processDialog-errors-title {
font-size: 1.5em;
- color: #000000;
+ color: #000;
margin-bottom: 2em;
}
.oo-ui-processDialog-error {
transition: opacity 250ms ease;
}
.oo-ui-windowManager-modal > .oo-ui-dialog > .oo-ui-window-frame {
- background-color: #ffffff;
+ background-color: #fff;
opacity: 0;
-webkit-transform: scale(0.5);
-moz-transform: scale(0.5);
bottom: 1em;
max-height: 100%;
max-height: calc(100% - 2em);
- border: 1px solid #cccccc;
+ border: 1px solid #ccc;
border-radius: 0.5em;
box-shadow: 0 0.2em 1em rgba(0, 0, 0, 0.3);
}
/*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
* 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-08-16T21:13:53Z
+ * Date: 2016-09-13T18:30:06Z
*/
.oo-ui-window {
background: transparent;
white-space: nowrap;
}
.oo-ui-messageDialog-content > .oo-ui-window-foot {
- outline: 1px solid #aaaaaa;
+ outline: 1px solid #aaa;
}
.oo-ui-messageDialog-title,
.oo-ui-messageDialog-message {
.oo-ui-messageDialog-title {
font-size: 1.5em;
line-height: 1;
- color: #000000;
+ color: #000;
}
.oo-ui-messageDialog-message {
font-size: 0.9em;
line-height: 1.25;
- color: #555555;
+ color: #222;
}
.oo-ui-messageDialog-message-verbose {
font-size: 1.1em;
text-align: left;
}
.oo-ui-messageDialog-actions-horizontal .oo-ui-actionWidget {
- border-right: 1px solid #cccccc;
+ border-right: 1px solid #9aa0a7;
margin: 0;
}
.oo-ui-messageDialog-actions-horizontal .oo-ui-actionWidget:last-child {
border-right-width: 0;
}
.oo-ui-messageDialog-actions-vertical .oo-ui-actionWidget {
- border-bottom: 1px solid #cccccc;
+ border-bottom: 1px solid #9aa0a7;
margin: 0;
}
.oo-ui-messageDialog-actions-vertical .oo-ui-actionWidget:last-child {
}
.oo-ui-processDialog-errors-title {
font-size: 1.5em;
- color: #000000;
+ color: #000;
margin-bottom: 2em;
}
.oo-ui-processDialog-error {
transition: opacity 250ms;
}
.oo-ui-windowManager-modal > .oo-ui-dialog > .oo-ui-window-frame {
- background-color: #ffffff;
+ background-color: #fff;
opacity: 0;
-webkit-transform: scale(0.5);
-moz-transform: scale(0.5);
bottom: 1em;
max-height: 100%;
max-height: calc(100% - 2em);
- border: 1px solid #aaaaaa;
+ border: 1px solid #a2a9b1;
border-radius: 2px;
box-shadow: 0 0.15em 0 0 rgba(0, 0, 0, 0.15);
}
/*!
- * OOjs UI v0.17.8
+ * OOjs UI v0.17.9
* 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-08-16T21:13:48Z
+ * Date: 2016-09-13T18:30:02Z
*/
( function ( OO ) {
return this;
};
+/* eslint-disable no-unused-vars */
/**
* ActionSets manage the behavior of the {@link OO.ui.ActionWidget action widgets} that comprise them.
* Actions can be made available for specific contexts (modes) and circumstances
this.changing = false;
this.changed = false;
};
+/* eslint-enable no-unused-vars */
/* Setup */
$body = $( this.getElementDocument().body ),
// We could have multiple window managers open so only modify
// the body css at the bottom of the stack
- stackDepth = $body.data( 'windowManagerGlobalEvents' ) || 0 ;
+ stackDepth = $body.data( 'windowManagerGlobalEvents' ) || 0;
on = on === undefined ? !!this.globalEvents : !!on;
"intro": "@import '../../../../src/styles/common';",
"variants": {
"invert": {
- "color": "#ffffff",
+ "color": "#fff",
"global": true
+ },
+ "progressive": {
+ "color": "#36c",
+ "global": true
+ },
+ "constructive": {
+ "color": "#36c"
+ },
+ "destructive": {
+ "color": "#c33"
+ },
+ "warning": {
+ "color": "#ff5d00"
}
},
"images": {
"intro": "@import '../../../../src/styles/common';",
"variants": {
"invert": {
- "color": "#ffffff",
+ "color": "#fff",
"global": true
+ },
+ "progressive": {
+ "color": "#36c",
+ "global": true
+ },
+ "constructive": {
+ "color": "#36c"
+ },
+ "destructive": {
+ "color": "#c33"
+ },
+ "warning": {
+ "color": "#ff5d00"
}
},
"images": {
"intro": "@import '../../../../src/styles/common';",
"variants": {
"invert": {
- "color": "#ffffff",
+ "color": "#fff",
"global": true
+ },
+ "progressive": {
+ "color": "#36c",
+ "global": true
+ },
+ "constructive": {
+ "color": "#36c"
+ },
+ "destructive": {
+ "color": "#c33"
+ },
+ "warning": {
+ "color": "#ff5d00"
}
},
"images": {
"intro": "@import '../../../../src/styles/common';",
"variants": {
"invert": {
- "color": "#ffffff",
+ "color": "#fff",
"global": true
},
"progressive": {
- "color": "#347bff"
+ "color": "#36c",
+ "global": true
},
"constructive": {
- "color": "#347bff"
+ "color": "#36c"
},
"destructive": {
- "color": "#d11d13"
+ "color": "#c33"
},
"warning": {
"color": "#ff5d00"
"intro": "@import '../../../../src/styles/common';",
"variants": {
"invert": {
- "color": "#ffffff",
+ "color": "#fff",
"global": true
+ },
+ "progressive": {
+ "color": "#36c",
+ "global": true
+ },
+ "constructive": {
+ "color": "#36c"
+ },
+ "destructive": {
+ "color": "#c33"
+ },
+ "warning": {
+ "color": "#ff5d00"
}
},
"images": {
"intro": "@import '../../../../src/styles/common';",
"variants": {
"invert": {
- "color": "#ffffff",
+ "color": "#fff",
"global": true
+ },
+ "progressive": {
+ "color": "#36c",
+ "global": true
+ },
+ "constructive": {
+ "color": "#36c"
+ },
+ "destructive": {
+ "color": "#c33"
+ },
+ "warning": {
+ "color": "#ff5d00"
}
},
"images": {
"intro": "@import '../../../../src/styles/common';",
"variants": {
"invert": {
- "color": "#ffffff",
+ "color": "#fff",
"global": true
+ },
+ "progressive": {
+ "color": "#36c",
+ "global": true
+ },
+ "constructive": {
+ "color": "#36c"
+ },
+ "destructive": {
+ "color": "#c33"
+ },
+ "warning": {
+ "color": "#ff5d00"
}
},
"images": {
"intro": "@import '../../../../src/styles/common';",
"variants": {
"invert": {
- "color": "#ffffff",
+ "color": "#fff",
"global": true
},
"progressive": {
- "color": "#347bff"
+ "color": "#36c",
+ "global": true
},
"constructive": {
- "color": "#347bff"
+ "color": "#36c"
},
"destructive": {
- "color": "#d11d13"
+ "color": "#c33"
},
"warning": {
"color": "#ff5d00"
"intro": "@import '../../../../src/styles/common';",
"variants": {
"invert": {
- "color": "#ffffff",
+ "color": "#fff",
"global": true
+ },
+ "progressive": {
+ "color": "#36c",
+ "global": true
+ },
+ "constructive": {
+ "color": "#36c"
+ },
+ "destructive": {
+ "color": "#c33"
+ },
+ "warning": {
+ "color": "#ff5d00"
}
},
"images": {
"intro": "@import '../../../../src/styles/common';",
"variants": {
"invert": {
- "color": "#ffffff",
+ "color": "#fff",
"global": true
+ },
+ "progressive": {
+ "color": "#36c",
+ "global": true
+ },
+ "constructive": {
+ "color": "#36c"
+ },
+ "destructive": {
+ "color": "#c33"
+ },
+ "warning": {
+ "color": "#ff5d00"
}
},
"images": {
"intro": "@import '../../../../src/styles/common';",
"variants": {
"invert": {
- "color": "#ffffff",
+ "color": "#fff",
"global": true
},
"progressive": {
- "color": "#347bff"
+ "color": "#36c",
+ "global": true
},
"constructive": {
- "color": "#347bff"
+ "color": "#36c"
},
"destructive": {
- "color": "#d11d13"
+ "color": "#c33"
},
"warning": {
"color": "#ff5d00"
"intro": "@import '../../../../src/styles/common';",
"variants": {
"invert": {
- "color": "#ffffff",
+ "color": "#fff",
"global": true
+ },
+ "progressive": {
+ "color": "#36c",
+ "global": true
+ },
+ "constructive": {
+ "color": "#36c"
+ },
+ "destructive": {
+ "color": "#c33"
+ },
+ "warning": {
+ "color": "#ff5d00"
}
},
"images": {
"intro": "@import '../../../../src/styles/common';",
"variants": {
"invert": {
- "color": "#FFFFFF",
+ "color": "#fff",
"global": true
+ },
+ "progressive": {
+ "color": "#36c",
+ "global": true
+ },
+ "constructive": {
+ "color": "#36c"
+ },
+ "destructive": {
+ "color": "#c33"
+ },
+ "warning": {
+ "color": "#ff5d00"
}
},
"images": {
"intro": "@import '../../../../src/styles/common';",
"variants": {
"invert": {
- "color": "#ffffff",
+ "color": "#fff",
"global": true
+ },
+ "progressive": {
+ "color": "#36c",
+ "global": true
+ },
+ "constructive": {
+ "color": "#36c"
+ },
+ "destructive": {
+ "color": "#c33"
+ },
+ "warning": {
+ "color": "#ff5d00"
}
},
"images": {
"intro": "@import '../../../../src/styles/common';",
"variants": {
"invert": {
- "color": "#ffffff",
+ "color": "#fff",
"global": true
},
"progressive": {
- "color": "#347bff"
+ "color": "#36c",
+ "global": true
},
"constructive": {
- "color": "#347bff"
- },
- "constructive-deprecated": {
- "color": "#00af89"
+ "color": "#36c"
},
"destructive": {
- "color": "#d11d13"
+ "color": "#c33"
},
"warning": {
"color": "#ff5d00"
"advanced": { "file": "images/icons/advanced.svg" },
"alert": { "file": "images/icons/alert.svg", "variants": [ "warning" ] },
"cancel": { "file": "images/icons/cancel.svg", "variants": [ "destructive" ] },
- "check": { "file": "images/icons/check.svg", "variants": [ "constructive-deprecated", "constructive", "progressive", "destructive" ] },
- "circle": { "file": "images/icons/circle.svg", "variants": [ "constructive-deprecated", "constructive", "progressive" ] },
+ "check": { "file": "images/icons/check.svg", "variants": [ "constructive", "progressive", "destructive" ] },
+ "circle": { "file": "images/icons/circle.svg", "variants": [ "constructive", "progressive" ] },
"close": { "file": {
"ltr": "images/icons/close-ltr.svg",
"rtl": "images/icons/close-rtl.svg"
"rtl": "images/icons/search-rtl.svg"
} },
"settings": { "file": "images/icons/settings.svg" },
- "tag": { "file": "images/icons/tag.svg", "variants": [ "destructive", "warning", "constructive", "progressive" ] },
+ "tag": { "file": "images/icons/tag.svg", "variants": [ "destructive", "warning", "constructive" ] },
"undo": { "file": {
"ltr": "images/icons/arched-arrow-rtl.svg",
"rtl": "images/icons/arched-arrow-ltr.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: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
<g id="add">
<path id="plus" d="M13 6h-2v5H6v2h5v5h2v-5h5v-2h-5z"/>
</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="add">
<path id="plus" d="M13 6h-2v5H6v2h5v5h2v-5h5v-2h-5z"/>
</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: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
<g id="add">
<path id="plus" d="M13 6h-2v5H6v2h5v5h2v-5h5v-2h-5z"/>
</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M20 13.44v-2.88l-1.8-.3c-.1-.397-.3-.794-.6-1.39l1.1-1.49-2.1-2.088-1.5 1.093c-.5-.298-1-.497-1.4-.596L13.5 4h-2.9l-.3 1.79c-.5.098-.9.297-1.4.595L7.4 5.292 5.3 7.38l1 1.49c-.3.496-.4.894-.6 1.39l-1.7.2v2.882l1.8.298c.1.497.3.894.6 1.39l-1 1.492 2.1 2.087 1.5-1c.4.2.9.395 1.4.594l.3 1.79h3l.3-1.79c.5-.1.9-.298 1.4-.596l1.5 1.092 2.1-2.08-1.1-1.49c.3-.496.5-.993.6-1.39l1.5-.3zm-8 1.492c-1.7 0-3-1.292-3-2.982 0-1.69 1.3-2.98 3-2.98s3 1.29 3 2.98-1.3 2.982-3 2.982z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M20 13.44v-2.88l-1.8-.3c-.1-.397-.3-.794-.6-1.39l1.1-1.49-2.1-2.088-1.5 1.093c-.5-.298-1-.497-1.4-.596L13.5 4h-2.9l-.3 1.79c-.5.098-.9.297-1.4.595L7.4 5.292 5.3 7.38l1 1.49c-.3.496-.4.894-.6 1.39l-1.7.2v2.882l1.8.298c.1.497.3.894.6 1.39l-1 1.492 2.1 2.087 1.5-1c.4.2.9.395 1.4.594l.3 1.79h3l.3-1.79c.5-.1.9-.298 1.4-.596l1.5 1.092 2.1-2.08-1.1-1.49c.3-.496.5-.993.6-1.39l1.5-.3zm-8 1.492c-1.7 0-3-1.292-3-2.982 0-1.69 1.3-2.98 3-2.98s3 1.29 3 2.98-1.3 2.982-3 2.982z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="alert">
<path id="point" d="M11 16h2v2h-2z"/>
<path id="stroke" d="M13.516 10h-3L11 15h2z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="alert">
+ <path id="point" d="M11 16h2v2h-2z"/>
+ <path id="stroke" d="M13.516 10h-3L11 15h2z"/>
+ <path id="triangle" d="M12.017 5.974L19.537 19H4.497l7.52-13.026m0-2.474c-.545 0-1.09.357-1.5 1.07L2.53 18.403C1.705 19.833 2.38 21 4.03 21H20c1.65 0 2.325-1.17 1.5-2.6L13.517 4.575c-.413-.715-.956-1.072-1.5-1.072z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M9 9h6c.554 0 1 .446 1 1v5c0 .554-.446 1-1 1H9c-.554 0-1-.446-1-1v-5c0-.554.446-1 1-1zm-5.5 9h17a.5.5 0 0 1 0 1h-17a.5.5 0 0 1 0-1zm0-12h17a.5.5 0 0 1 0 1h-17a.5.5 0 0 1 0-1z" id="align-center"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M9 9h6c.554 0 1 .446 1 1v5c0 .554-.446 1-1 1H9c-.554 0-1-.446-1-1v-5c0-.554.446-1 1-1zm-5.5 9h17a.5.5 0 0 1 0 1h-17a.5.5 0 0 1 0-1zm0-12h17a.5.5 0 0 1 0 1h-17a.5.5 0 0 1 0-1z" id="align-center"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M4 9h6c.554 0 1 .446 1 1v5c0 .554-.446 1-1 1H4c-.554 0-1-.446-1-1v-5c0-.554.446-1 1-1zm9.5 0h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1zm0 3h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1zm0 3h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1zm-10-9h17a.5.5 0 0 1 0 1h-17a.5.5 0 0 1 0-1zm0 12h17a.5.5 0 0 1 0 1h-17a.5.5 0 0 1 0-1z" id="align-float-left"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M4 9h6c.554 0 1 .446 1 1v5c0 .554-.446 1-1 1H4c-.554 0-1-.446-1-1v-5c0-.554.446-1 1-1zm9.5 0h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1zm0 3h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1zm0 3h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1zm-10-9h17a.5.5 0 0 1 0 1h-17a.5.5 0 0 1 0-1zm0 12h17a.5.5 0 0 1 0 1h-17a.5.5 0 0 1 0-1z" id="align-float-left"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M20 9h-6c-.554 0-1 .446-1 1v5c0 .554.446 1 1 1h6c.554 0 1-.446 1-1v-5c0-.554-.446-1-1-1zm-9.5 0h-7a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1zm0 3h-7a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1zm0 3h-7a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1zm10-9h-17a.5.5 0 0 0 0 1h17a.5.5 0 0 0 0-1zm0 12h-17a.5.5 0 0 0 0 1h17a.5.5 0 0 0 0-1z" id="align-float-right"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M20 9h-6c-.554 0-1 .446-1 1v5c0 .554.446 1 1 1h6c.554 0 1-.446 1-1v-5c0-.554-.446-1-1-1zm-9.5 0h-7a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1zm0 3h-7a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1zm0 3h-7a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1zm10-9h-17a.5.5 0 0 0 0 1h17a.5.5 0 0 0 0-1zm0 12h-17a.5.5 0 0 0 0 1h17a.5.5 0 0 0 0-1z" id="align-float-right"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M13.3 6.3l6.3 5.7-6.3 5.7v-3.8H12c-3.2 0-6.3 1.3-7.6 3.8 0-4.7 2.8-7.6 7.9-7.6h.9V6.3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M13.3 6.3l6.3 5.7-6.3 5.7v-3.8H12c-3.2 0-6.3 1.3-7.6 3.8 0-4.7 2.8-7.6 7.9-7.6h.9V6.3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M10.7 6.3L4.4 12l6.3 5.7v-3.8H12c3.2 0 6.3 1.3 7.6 3.8 0-4.7-2.8-7.6-7.9-7.6h-.9V6.3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M10.7 6.3L4.4 12l6.3 5.7v-3.8H12c3.2 0 6.3 1.3 7.6 3.8 0-4.7-2.8-7.6-7.9-7.6h-.9V6.3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M16 12H6c-1.7 0-3 1.3-3 3h13v3l5-4.5L16 9v3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M16 12H6c-1.7 0-3 1.3-3 3h13v3l5-4.5L16 9v3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M8 12h10c1.7 0 3 1.3 3 3H8v3l-5-4.5L8 9v3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M8 12h10c1.7 0 3 1.3 3 3H8v3l-5-4.5L8 9v3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M12 10h4V5h-4v5zm-5 2h9v-1H7v1zm0 2h9v-1H7v1zm0 2h9v-1H7v1zm4-9H7v1h4V7zm0 2H7v1h4V9zm0-4H7v1h4V5zM5 3h13v16H8c-1.7 0-3-1.3-3-3V3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M12 10h4V5h-4v5zm-5 2h9v-1H7v1zm0 2h9v-1H7v1zm0 2h9v-1H7v1zm4-9H7v1h4V7zm0 2H7v1h4V9zm0-4H7v1h4V5zM5 3h13v16H8c-1.7 0-3-1.3-3-3V3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M11 10H7V5h4v5zm5 2H7v-1h9v1zm0 2H7v-1h9v1zm0 2H7v-1h9v1zm-4-9h4v1h-4V7zm0 2h4v1h-4V9zm0-4h4v1h-4V5zm6-2H5v16h10c1.7 0 3-1.3 3-3V3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M11 10H7V5h4v5zm5 2H7v-1h9v1zm0 2H7v-1h9v1zm0 2H7v-1h9v1zm-4-9h4v1h-4V7zm0 2h4v1h-4V9zm0-4h4v1h-4V5zm6-2H5v16h10c1.7 0 3-1.3 3-3V3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M21 11l-6 7-4-4-1 1 5 5 7-8z"/>
<path d="M17 14V3H4v13c0 1.7 1.3 3 3 3h5l-3-3H6v-1h2.6l1-1H6v-1h9v1h-2l1 1h2l1-1zM6 5h4v1H6V5zm0 2h4v1H6V7zm0 2h4v1H6V9zm9 3H6v-1h9v1zm-4-2V5h4v5h-4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M21 11l-6 7-4-4-1 1 5 5 7-8z"/>
+ <path d="M17 14V3H4v13c0 1.7 1.3 3 3 3h5l-3-3H6v-1h2.6l1-1H6v-1h9v1h-2l1 1h2l1-1zM6 5h4v1H6V5zm0 2h4v1H6V7zm0 2h4v1H6V9zm9 3H6v-1h9v1zm-4-2V5h4v5h-4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M5 11l6 7 4-4 1 1-5 5-7-8z"/>
<path d="M9 14V3h13v13c0 1.7-1.3 3-3 3h-5l3-3h3v-1h-2.6l-1-1H20v-1h-9v1h2l-1 1h-2l-1-1zm11-9h-4v1h4V5zm0 2h-4v1h4V7zm0 2h-4v1h4V9zm-9 3h9v-1h-9v1zm4-2V5h-4v5h4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M5 11l6 7 4-4 1 1-5 5-7-8z"/>
+ <path d="M9 14V3h13v13c0 1.7-1.3 3-3 3h-5l3-3h3v-1h-2.6l-1-1H20v-1h-9v1h2l-1 1h-2l-1-1zm11-9h-4v1h4V5zm0 2h-4v1h4V7zm0 2h-4v1h4V9zm-9 3h9v-1h-9v1zm4-2V5h-4v5h4z"/>
+</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="article-redirect">
<path id="arrow" d="M18.1 14.2L23 18l-4.9 4.8v-2.2c-1.7 0-2.9-.2-4.3-1.2-1.2-.8-2.5-2.6-2.3-4.1 1.4 1 2.9 1.5 4.4 1.5.7 0 1.4-.1 2.1-.3l.1-2.3"/>
<path id="page" d="M5 3v13c0 1.7 1.3 3 3 3h3.375c-.157-.205-.3-.43-.438-.656-.42-.688-.77-1.483-.843-2.344H7v-1h3.125l.125-1H7v-1h3.375l.03-.188.283.188H16v1h-3.906l.22.156c.523.375 1.065.64 1.592.844H16v.406c.208-.013.418-.07.625-.094.068-1.294.125-3.874.125-3.874l1.25.968V3H5zm2 2h4v1H7V5zm5 0h4v5h-4V5zM7 7h4v1H7V7zm0 2h4v1H7V9zm0 2h9v1H7v-1z"/>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+ <g id="article-redirect">
+ <path id="arrow" d="M18.1 14.2L23 18l-4.9 4.8v-2.2c-1.7 0-2.9-.2-4.3-1.2-1.2-.8-2.5-2.6-2.3-4.1 1.4 1 2.9 1.5 4.4 1.5.7 0 1.4-.1 2.1-.3l.1-2.3"/>
+ <path id="page" d="M5 3v13c0 1.7 1.3 3 3 3h3.375c-.157-.205-.3-.43-.438-.656-.42-.688-.77-1.483-.843-2.344H7v-1h3.125l.125-1H7v-1h3.375l.03-.188.283.188H16v1h-3.906l.22.156c.523.375 1.065.64 1.592.844H16v.406c.208-.013.418-.07.625-.094.068-1.294.125-3.874.125-3.874l1.25.968V3H5zm2 2h4v1H7V5zm5 0h4v5h-4V5zM7 7h4v1H7V7zm0 2h4v1H7V9zm0 2h9v1H7v-1z"/>
+ </g>
+</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="article-redirect">
<path id="arrow" d="M5.9 14.2L1 18l4.9 4.8v-2.2c1.7 0 2.9-.2 4.3-1.2 1.2-.8 2.5-2.6 2.3-4.1-1.4 1-2.9 1.5-4.4 1.5-.7 0-1.4-.1-2.1-.3l-.1-2.3"/>
<path id="page" d="M19 3v13c0 1.7-1.3 3-3 3h-3.375c.157-.205.3-.43.438-.656.42-.688.77-1.483.843-2.344H17v-1h-3.125l-.125-1H17v-1h-3.375l-.03-.188-.283.188H8v1h3.906l-.22.156a7.097 7.097 0 0 1-1.592.844H8v.406c-.208-.013-.418-.07-.625-.094a178.903 178.903 0 0 1-.125-3.874L6 12.405V3zm-2 2h-4v1h4zm-5 0H8v5h4zm5 2h-4v1h4zm0 2h-4v1h4zm0 2H8v1h9z"/>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+ <g id="article-redirect">
+ <path id="arrow" d="M5.9 14.2L1 18l4.9 4.8v-2.2c1.7 0 2.9-.2 4.3-1.2 1.2-.8 2.5-2.6 2.3-4.1-1.4 1-2.9 1.5-4.4 1.5-.7 0-1.4-.1-2.1-.3l-.1-2.3"/>
+ <path id="page" d="M19 3v13c0 1.7-1.3 3-3 3h-3.375c.157-.205.3-.43.438-.656.42-.688.77-1.483.843-2.344H17v-1h-3.125l-.125-1H17v-1h-3.375l-.03-.188-.283.188H8v1h3.906l-.22.156a7.097 7.097 0 0 1-1.592.844H8v.406c-.208-.013-.418-.07-.625-.094a178.903 178.903 0 0 1-.125-3.874L6 12.405V3zm-2 2h-4v1h4zm-5 0H8v5h4zm5 2h-4v1h4zm0 2h-4v1h4zm0 2H8v1h9z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M19.1 18.5c.6-.7.9-1.5.9-2.5 0-2.2-1.8-4-4-4s-4 1.8-4 4 1.8 4 4 4c.7 0 1.3-.1 1.8-.4l2.7 2.7 1.1-1.1-2.5-2.7zm-3.1-.3c-1.2 0-2.2-1-2.2-2.3 0-1.2 1-2.2 2.2-2.2 1.2 0 2.3 1 2.3 2.2-.1 1.3-1.1 2.3-2.3 2.3zM11.8 13c.3-.4.6-.7 1-1H7v-1h9s1.2 0 2 .6V3H5v13c0 1.7 1.3 3 3 3h3.8c-.6-.8-1-1.9-1-3H7v-1h3.9l.3-1H7v-1h4.8zm.2-8h4v5h-4V5zM7 5h4v1H7V5zm0 2h4v1H7V7zm0 2h4v1H7V9z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M19.1 18.5c.6-.7.9-1.5.9-2.5 0-2.2-1.8-4-4-4s-4 1.8-4 4 1.8 4 4 4c.7 0 1.3-.1 1.8-.4l2.7 2.7 1.1-1.1-2.5-2.7zm-3.1-.3c-1.2 0-2.2-1-2.2-2.3 0-1.2 1-2.2 2.2-2.2 1.2 0 2.3 1 2.3 2.2-.1 1.3-1.1 2.3-2.3 2.3zM11.8 13c.3-.4.6-.7 1-1H7v-1h9s1.2 0 2 .6V3H5v13c0 1.7 1.3 3 3 3h3.8c-.6-.8-1-1.9-1-3H7v-1h3.9l.3-1H7v-1h4.8zm.2-8h4v5h-4V5zM7 5h4v1H7V5zm0 2h4v1H7V7zm0 2h4v1H7V9z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M7.5 18.5c-.6-.7-.9-1.5-.9-2.5 0-2.2 1.8-4 4-4s4 1.8 4 4-1.8 4-4 4c-.7 0-1.3-.1-1.8-.4l-2.7 2.7L5 21.2l2.5-2.7zm3.1-.3c1.2 0 2.2-1 2.2-2.3 0-1.2-1-2.2-2.2-2.2-1.2 0-2.3 1-2.3 2.2.1 1.3 1.1 2.3 2.3 2.3zm4.2-5.2c-.3-.4-.6-.7-1-1h5.8v-1h-9s-1.2 0-2 .6V3h13v13c0 1.7-1.3 3-3 3h-3.8c.6-.8 1-1.9 1-3h3.8v-1h-3.9l-.3-1h4.2v-1h-4.8zm-.2-8h-4v5h4V5zm5 0h-4v1h4V5zm0 2h-4v1h4V7zm0 2h-4v1h4V9z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M7.5 18.5c-.6-.7-.9-1.5-.9-2.5 0-2.2 1.8-4 4-4s4 1.8 4 4-1.8 4-4 4c-.7 0-1.3-.1-1.8-.4l-2.7 2.7L5 21.2l2.5-2.7zm3.1-.3c1.2 0 2.2-1 2.2-2.3 0-1.2-1-2.2-2.2-2.2-1.2 0-2.3 1-2.3 2.2.1 1.3 1.1 2.3 2.3 2.3zm4.2-5.2c-.3-.4-.6-.7-1-1h5.8v-1h-9s-1.2 0-2 .6V3h13v13c0 1.7-1.3 3-3 3h-3.8c.6-.8 1-1.9 1-3h3.8v-1h-3.9l-.3-1h4.2v-1h-4.8zm-.2-8h-4v5h4V5zm5 0h-4v1h4V5zm0 2h-4v1h4V7zm0 2h-4v1h4V9z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M11 12h4V7h-4v5zm-5 2h9v-1H6v1zm0 2h9v-1H6v1zm0 2h9v-1H6v1zm4-9H6v1h4V9zm0 2H6v1h4v-1zm0-4H6v1h4V7zM4 5h13v16H7c-1.7 0-3-1.3-3-3V5z"/>
<path d="M18 4v14h2V2H7v2" fill-rule="evenodd"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M11 12h4V7h-4v5zm-5 2h9v-1H6v1zm0 2h9v-1H6v1zm0 2h9v-1H6v1zm4-9H6v1h4V9zm0 2H6v1h4v-1zm0-4H6v1h4V7zM4 5h13v16H7c-1.7 0-3-1.3-3-3V5z"/>
+ <path d="M18 4v14h2V2H7v2" fill-rule="evenodd"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M13 12H9V7h4v5zm5 2H9v-1h9v1zm0 2H9v-1h9v1zm0 2H9v-1h9v1zm-4-9h4v1h-4V9zm0 2h4v1h-4v-1zm0-4h4v1h-4V7zm6-2H7v16h10c1.7 0 3-1.3 3-3V5z"/>
<path d="M6 4v14H4V2h13v2" fill-rule="evenodd"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M13 12H9V7h4v5zm5 2H9v-1h9v1zm0 2H9v-1h9v1zm0 2H9v-1h9v1zm-4-9h4v1h-4V9zm0 2h4v1h-4v-1zm0-4h4v1h-4V7zm6-2H7v16h10c1.7 0 3-1.3 3-3V5z"/>
+ <path d="M6 4v14H4V2h13v2" fill-rule="evenodd"/>
+</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="-293 385 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-293 385 24 24"><style>* { fill: #fff }</style>
<path d="M-274.3 390.9c-1.6-1.6-4.3-1.5-5.8.1l.2.2c.5.5 1.3.7 2.1.4.8-.3 1.7-.1 2.4.6 1 .9.9 2.4 0 3.4l-7.1 7.1c-.9 1-2.4.9-3.4 0s-.9-2.4 0-3.4l4.4-4.4c.3-.3.9-.5 1.3-.1s.2 1-.1 1.3l-3.4 3.4c-.6.6-.6 1.7.1 2.3l4.3-4.3c.8-.8 1.1-1.8.9-2.7-.2-.9-.9-1.6-1.7-1.9-.9-.2-1.9 0-2.6.7l-4.4 4.4c-1.6 1.6-1.6 4.3.1 5.8 1.5 1.6 4.3 1.5 5.8-.1l7-7c.8-.8 1.2-1.9 1.2-3s-.5-2.1-1.3-2.8c-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-1.5-1.6.8.7 0 0z"/>
</svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-293 385 24 24"><style>* { fill: #36c }</style>
+ <path d="M-274.3 390.9c-1.6-1.6-4.3-1.5-5.8.1l.2.2c.5.5 1.3.7 2.1.4.8-.3 1.7-.1 2.4.6 1 .9.9 2.4 0 3.4l-7.1 7.1c-.9 1-2.4.9-3.4 0s-.9-2.4 0-3.4l4.4-4.4c.3-.3.9-.5 1.3-.1s.2 1-.1 1.3l-3.4 3.4c-.6.6-.6 1.7.1 2.3l4.3-4.3c.8-.8 1.1-1.8.9-2.7-.2-.9-.9-1.6-1.7-1.9-.9-.2-1.9 0-2.6.7l-4.4 4.4c-1.6 1.6-1.6 4.3.1 5.8 1.5 1.6 4.3 1.5 5.8-.1l7-7c.8-.8 1.2-1.9 1.2-3s-.5-2.1-1.3-2.8c-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-.7-.7.8.7 0 0-1.5-1.6.8.7 0 0z"/>
+</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="-119 70 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-119 70 24 24"><style>* { fill: #fff }</style>
<path d="M-113.3 75.6c-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7-1.3 1.7-1.3 2.8 0 1.1.4 2.2 1.2 3l7 7c1.5 1.6 4.3 1.7 5.8.1 1.7-1.5 1.7-4.2.1-5.8l-4.4-4.4c-.7-.7-1.7-.9-2.6-.7-.8.3-1.5 1-1.7 1.9-.2.9.1 1.9.9 2.7l4.3 4.3c.7-.6.7-1.7.1-2.3l-3.4-3.4c-.3-.3-.5-.9-.1-1.3s1-.2 1.3.1l4.4 4.4c.9 1 1 2.5 0 3.4s-2.5 1-3.4 0l-7.1-7.1c-.9-1-1-2.5 0-3.4.7-.7 1.6-.9 2.4-.6.8.3 1.6.1 2.1-.4l.2-.2c-1.5-1.6-4.2-1.8-5.8-.1-.8.7 1.5-1.7 0 0z"/>
</svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-119 70 24 24"><style>* { fill: #36c }</style>
+ <path d="M-113.3 75.6c-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7.7-.7 0 0-.8.7-1.3 1.7-1.3 2.8 0 1.1.4 2.2 1.2 3l7 7c1.5 1.6 4.3 1.7 5.8.1 1.7-1.5 1.7-4.2.1-5.8l-4.4-4.4c-.7-.7-1.7-.9-2.6-.7-.8.3-1.5 1-1.7 1.9-.2.9.1 1.9.9 2.7l4.3 4.3c.7-.6.7-1.7.1-2.3l-3.4-3.4c-.3-.3-.5-.9-.1-1.3s1-.2 1.3.1l4.4 4.4c.9 1 1 2.5 0 3.4s-2.5 1-3.4 0l-7.1-7.1c-.9-1-1-2.5 0-3.4.7-.7 1.6-.9 2.4-.6.8.3 1.6.1 2.1-.4l.2-.2c-1.5-1.6-4.2-1.8-5.8-.1-.8.7 1.5-1.7 0 0z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M17.5 13V8c0-3-2.3-5-5.5-5S6.5 5 6.5 8v5c0 2 0 3-2 3v1h15v-1c-2 0-2-1-2-3zM12 19H9c0 1 1.6 2 3 2s3-1 3-2h-3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M17.5 13V8c0-3-2.3-5-5.5-5S6.5 5 6.5 8v5c0 2 0 3-2 3v1h15v-1c-2 0-2-1-2-3zM12 19H9c0 1 1.6 2 3 2s3-1 3-2h-3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M17.8 13.7L19.5 9c1-2.8-.5-5.5-3.5-6.6-3-1.1-5.9 0-6.9 2.8L7.4 9.9c-.7 1.9-1 2.8-2.9 2.1l-.3 1 14.1 5.1.3-.9c-1.9-.7-1.5-1.6-.8-3.5zM12 18.8l-2.8-1c-.3.9.8 2.4 2.1 2.9s3.2.1 3.5-.9l-2.8-1z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M17.8 13.7L19.5 9c1-2.8-.5-5.5-3.5-6.6-3-1.1-5.9 0-6.9 2.8L7.4 9.9c-.7 1.9-1 2.8-2.9 2.1l-.3 1 14.1 5.1.3-.9c-1.9-.7-1.5-1.6-.8-3.5zM12 18.8l-2.8-1c-.3.9.8 2.4 2.1 2.9s3.2.1 3.5-.9l-2.8-1z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M6.21 13.7L4.51 9c-1-2.8.5-5.5 3.5-6.6 3-1.1 5.9 0 6.9 2.8l1.7 4.7c.7 1.9 1 2.8 2.9 2.1l.3 1-14.1 5.1-.3-.9c1.9-.7 1.5-1.6.8-3.5zm5.8 5.1l2.8-1c.3.9-.8 2.4-2.1 2.9s-3.2.1-3.5-.9l2.8-1z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M6.21 13.7L4.51 9c-1-2.8.5-5.5 3.5-6.6 3-1.1 5.9 0 6.9 2.8l1.7 4.7c.7 1.9 1 2.8 2.9 2.1l.3 1-14.1 5.1-.3-.9c1.9-.7 1.5-1.6.8-3.5zm5.8 5.1l2.8-1c.3.9-.8 2.4-2.1 2.9s-3.2.1-3.5-.9l2.8-1z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</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-8zm4 12l-3-2-1 4-1-4-3 2 2-3-4-1 4-1-2-3 3 2 1-4 1 4 3-2-2 3 4 1-4 1 2 3z"/>
</svg>
--- /dev/null
+<?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: #36c }</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-8zm4 12l-3-2-1 4-1-4-3 2 2-3-4-1 4-1-2-3 3 2 1-4 1 4 3-2-2 3 4 1-4 1 2 3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M15.3 14.7C16.1 10.9 14.7 4 12 4c-2.7 0-4.2 6.7-3.4 10.5L7 18h2.7l.3 1h4c.2-.3.1-.5.3-1H17l-1.7-3.3zM12 10c-.8 0-1.5-.7-1.5-1.5S11.2 7 12 7s1.5.7 1.5 1.5S12.8 10 12 10zm2 10c0 1.1-2 2-2 2s-2-.9-2-2"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M15.3 14.7C16.1 10.9 14.7 4 12 4c-2.7 0-4.2 6.7-3.4 10.5L7 18h2.7l.3 1h4c.2-.3.1-.5.3-1H17l-1.7-3.3zM12 10c-.8 0-1.5-.7-1.5-1.5S11.2 7 12 7s1.5.7 1.5 1.5S12.8 10 12 10zm2 10c0 1.1-2 2-2 2s-2-.9-2-2"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z" id="a"/>
<g id="up">
<path id="arrow" d="M15.5 9h7L19 3z"/>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z" id="a"/>
+ <g id="up">
+ <path id="arrow" d="M15.5 9h7L19 3z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z" id="a"/>
<g id="up">
<path id="arrow" d="M1.5 9h7L5 3z"/>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z" id="a"/>
+ <g id="up">
+ <path id="arrow" d="M1.5 9h7L5 3z"/>
+ </g>
+</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: #d11d13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</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>
--- /dev/null
+<?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: #36c }</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M17 11v2h-2l3.6 3.6c.9-1.3 1.4-2.9 1.4-4.6 0-4.4-3.6-8-8-8-1.7 0-3.3.5-4.6 1.4L13 11h4zM4 4L3 5l2.4 2.4C4.5 8.7 4 10.3 4 12c0 4.4 3.6 8 8 8 1.7 0 3.3-.5 4.6-1.4L19 21l1-1L4 4zm3 9v-2h2l2 2H7z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M17 11v2h-2l3.6 3.6c.9-1.3 1.4-2.9 1.4-4.6 0-4.4-3.6-8-8-8-1.7 0-3.3.5-4.6 1.4L13 11h4zM4 4L3 5l2.4 2.4C4.5 8.7 4 10.3 4 12c0 4.4 3.6 8 8 8 1.7 0 3.3-.5 4.6-1.4L19 21l1-1L4 4zm3 9v-2h2l2 2H7z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M7 11v2h2l-3.6 3.6C4.5 15.3 4 13.7 4 12c0-4.4 3.6-8 8-8 1.7 0 3.3.5 4.6 1.4L11 11H7zm13-7l1 1-2.4 2.4c.9 1.3 1.4 2.9 1.4 4.6 0 4.4-3.6 8-8 8-1.7 0-3.3-.5-4.6-1.4L5 21l-1-1L20 4zm-3 9v-2h-2l-2 2h4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M7 11v2h2l-3.6 3.6C4.5 15.3 4 13.7 4 12c0-4.4 3.6-8 8-8 1.7 0 3.3.5 4.6 1.4L11 11H7zm13-7l1 1-2.4 2.4c.9 1.3 1.4 2.9 1.4 4.6 0 4.4-3.6 8-8 8-1.7 0-3.3-.5-4.6-1.4L5 21l-1-1L20 4zm-3 9v-2h-2l-2 2h4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M16 18h3L14 6h-3L6 18h3l1.25-3h4.5L16 18zm-4.917-5L12.5 9.6l1.417 3.4h-2.834z" id="bold-a"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M16 18h3L14 6h-3L6 18h3l1.25-3h4.5L16 18zm-4.917-5L12.5 9.6l1.417 3.4h-2.834z" id="bold-a"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="bold-arab-ain">
<path id="arab-ain" d="M9.337 13.616c0 1.35 1.386 2.1 4.16 2.258l2.186-.03.318.045c-.03.12-.25.34-.66.65l-.09.06c-1.233.93-2.42 1.394-3.56 1.394-1.14 0-2.043-.33-2.71-.99-.65-.66-.973-1.56-.973-2.7.006-1.353.567-2.572 1.685-3.657v-.044l-.606-.55a.952.952 0 0 1-.222-.63c0-.49.24-1.11.72-1.863.65-1.045 1.302-1.565 1.957-1.56.886.005 1.618.42 2.194 1.246.325.48-.03.55-1.064.22-.843-.33-1.528-.05-2.055.826l.016.074 1.125.866.05.005c1.405-.497 2.42-.74 3.044-.725-.06.116-.14.36-.244.732a27.75 27.75 0 0 1-.304.982l-.125.372-.386.05c-1.743.24-2.992.716-3.745 1.43-.465.463-.7.972-.704 1.524"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="bold-arab-ain">
+ <path id="arab-ain" d="M9.337 13.616c0 1.35 1.386 2.1 4.16 2.258l2.186-.03.318.045c-.03.12-.25.34-.66.65l-.09.06c-1.233.93-2.42 1.394-3.56 1.394-1.14 0-2.043-.33-2.71-.99-.65-.66-.973-1.56-.973-2.7.006-1.353.567-2.572 1.685-3.657v-.044l-.606-.55a.952.952 0 0 1-.222-.63c0-.49.24-1.11.72-1.863.65-1.045 1.302-1.565 1.957-1.56.886.005 1.618.42 2.194 1.246.325.48-.03.55-1.064.22-.843-.33-1.528-.05-2.055.826l.016.074 1.125.866.05.005c1.405-.497 2.42-.74 3.044-.725-.06.116-.14.36-.244.732a27.75 27.75 0 0 1-.304.982l-.125.372-.386.05c-1.743.24-2.992.716-3.745 1.43-.465.463-.7.972-.704 1.524"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="bold-arab-dad">
<path id="arab-dad" d="M16.41 8.232l-1.675-.665L15.43 6l1.687.64-.707 1.592m.775 3.078c-.51-.286-1-.427-1.476-.423-.478 0-.99.205-1.54.616l-.505.38.006.024c1.085.066 1.935.1 2.55.1h.315c.57-.022.994-.065 1.278-.132-.067-.17-.275-.36-.625-.566h-.006M10.38 14.6c-.016-.905-.33-1.87-.937-2.9l1.294-1.73.118.15c.267.337.504.925.713 1.767l.064.05c.496-.007.942-.17 1.338-.484v-.006l1.732-1.53c.68-.6 1.282-.9 1.807-.9.383.003.85.194 1.394.57.55.378.884.697 1 .96.063.15.094.385.094.71 0 .694-.11 1.227-.33 1.596-.193.31-.474.555-.845.734-.438.208-1.55.312-3.333.312-.8 0-1.794-.02-2.982-.064l-.142.43c-.254.67-.463 1.112-.625 1.323-.724.937-1.785 1.405-3.182 1.405-1.71-.006-2.56-.92-2.56-2.74.003-.94.278-1.814.824-2.618.15-.216.298-.367.444-.454.225-.133.288-.09.188.124-.396.862-.596 1.548-.6 2.058.008 1.177.752 1.768 2.232 1.772 1.038-.004 1.803-.182 2.295-.535"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="bold-arab-dad">
+ <path id="arab-dad" d="M16.41 8.232l-1.675-.665L15.43 6l1.687.64-.707 1.592m.775 3.078c-.51-.286-1-.427-1.476-.423-.478 0-.99.205-1.54.616l-.505.38.006.024c1.085.066 1.935.1 2.55.1h.315c.57-.022.994-.065 1.278-.132-.067-.17-.275-.36-.625-.566h-.006M10.38 14.6c-.016-.905-.33-1.87-.937-2.9l1.294-1.73.118.15c.267.337.504.925.713 1.767l.064.05c.496-.007.942-.17 1.338-.484v-.006l1.732-1.53c.68-.6 1.282-.9 1.807-.9.383.003.85.194 1.394.57.55.378.884.697 1 .96.063.15.094.385.094.71 0 .694-.11 1.227-.33 1.596-.193.31-.474.555-.845.734-.438.208-1.55.312-3.333.312-.8 0-1.794-.02-2.982-.064l-.142.43c-.254.67-.463 1.112-.625 1.323-.724.937-1.785 1.405-3.182 1.405-1.71-.006-2.56-.92-2.56-2.74.003-.94.278-1.814.824-2.618.15-.216.298-.367.444-.454.225-.133.288-.09.188.124-.396.862-.596 1.548-.6 2.058.008 1.177.752 1.768 2.232 1.772 1.038-.004 1.803-.182 2.295-.535"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="bold-armn-to">
<path id="armn-to" d="M13.86 16.257c.124 0 .254-.026.39-.078.135-.06.257-.15.367-.28a1.43 1.43 0 0 0 .273-.517c.073-.214.11-.48.11-.798V13h-1.14c-.14 0-.284.026-.43.078a.905.905 0 0 0-.383.258c-.11.125-.2.294-.274.508-.067.213-.1.487-.1.82 0 .34.035.47.108.695.08.21.18.39.29.53.12.13.25.23.39.29.14.05.276.07.406.07m-2.97-7.84a2.67 2.67 0 0 0-.975.45 2.1 2.1 0 0 0-.672.813c-.16.342-.242.78-.242 1.31V18H6v-7.188c0-.776.15-1.455.453-2.04a4.227 4.227 0 0 1 1.234-1.467c.52-.39 1.13-.685 1.83-.883a8.114 8.114 0 0 1 2.225-.297c.526 0 1.04.044 1.54.133.504.088.98.22 1.43.398.447.172.858.388 1.233.65.375.26.698.564.97.913.275.34.49.73.64 1.17.15.43.226 1.09.226 1.61h1.36v2.04h-1.36v1.6c0 .58-.102 1.09-.31 1.54-.21.44-.49.81-.844 1.11-.35.302-.834.53-1.297.687-.465.15-.954.226-1.47.226-.51 0-.997-.08-1.46-.235a3.46 3.46 0 0 1-1.22-.703 3.452 3.452 0 0 1-.836-1.174c-.203-.472-.304-1.027-.304-1.662s.1-1.18.32-1.64c.21-.46.49-.684.85-.976.35-.297.76-.513 1.22-.648.452-.14.93-.21 1.43-.21h1.13c-.01-.49-.04-1.044-.24-1.36a2.26 2.26 0 0 0-.77-.767 3.234 3.234 0 0 0-.986-.427c-.375-.09-.578-.094-1.1-.094-.52 0-.64.02-1.01.102z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="bold-armn-to">
+ <path id="armn-to" d="M13.86 16.257c.124 0 .254-.026.39-.078.135-.06.257-.15.367-.28a1.43 1.43 0 0 0 .273-.517c.073-.214.11-.48.11-.798V13h-1.14c-.14 0-.284.026-.43.078a.905.905 0 0 0-.383.258c-.11.125-.2.294-.274.508-.067.213-.1.487-.1.82 0 .34.035.47.108.695.08.21.18.39.29.53.12.13.25.23.39.29.14.05.276.07.406.07m-2.97-7.84a2.67 2.67 0 0 0-.975.45 2.1 2.1 0 0 0-.672.813c-.16.342-.242.78-.242 1.31V18H6v-7.188c0-.776.15-1.455.453-2.04a4.227 4.227 0 0 1 1.234-1.467c.52-.39 1.13-.685 1.83-.883a8.114 8.114 0 0 1 2.225-.297c.526 0 1.04.044 1.54.133.504.088.98.22 1.43.398.447.172.858.388 1.233.65.375.26.698.564.97.913.275.34.49.73.64 1.17.15.43.226 1.09.226 1.61h1.36v2.04h-1.36v1.6c0 .58-.102 1.09-.31 1.54-.21.44-.49.81-.844 1.11-.35.302-.834.53-1.297.687-.465.15-.954.226-1.47.226-.51 0-.997-.08-1.46-.235a3.46 3.46 0 0 1-1.22-.703 3.452 3.452 0 0 1-.836-1.174c-.203-.472-.304-1.027-.304-1.662s.1-1.18.32-1.64c.21-.46.49-.684.85-.976.35-.297.76-.513 1.22-.648.452-.14.93-.21 1.43-.21h1.13c-.01-.49-.04-1.044-.24-1.36a2.26 2.26 0 0 0-.77-.767 3.234 3.234 0 0 0-.986-.427c-.375-.09-.578-.094-1.1-.094-.52 0-.64.02-1.01.102z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="bold-b">
<path id="b" d="M7 18h6c2 0 4-1 4-3 0-1.064.01-1.975-1.99-3 2-.975 1.99-1.935 1.99-3 0-2-2-3-4-3H7v12zm7-8c0 1 0 1-2 1h-2V8h2c2 0 2 0 2 1v1zm-2 6h-2v-3h2c2 0 2 0 2 1v1s0 1-2 1z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="bold-b">
+ <path id="b" d="M7 18h6c2 0 4-1 4-3 0-1.064.01-1.975-1.99-3 2-.975 1.99-1.935 1.99-3 0-2-2-3-4-3H7v12zm7-8c0 1 0 1-2 1h-2V8h2c2 0 2 0 2 1v1zm-2 6h-2v-3h2c2 0 2 0 2 1v1s0 1-2 1z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="bold-cyrl-be">
<path id="cyrl-be" d="M7 6h9v2h-6v3h2.65c.892 0 1.632.11 2.22.327.587.218 1.087.622 1.5 1.21.42.59.63 1.188.63 1.98 0 .812-.21 1.397-.63 1.976-.418.578-.897.974-1.436 1.187-.533.213-1.295.32-2.286.32h-5.65m4.768-2c.75 0 1.28-.05 1.584-.12.305-.077.57-.247.792-.51.23-.26.343-.472.343-.854 0-.557-.2-.868-.596-1.12-.4-.255-1.07-.397-2.02-.397H10v3"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="bold-cyrl-be">
+ <path id="cyrl-be" d="M7 6h9v2h-6v3h2.65c.892 0 1.632.11 2.22.327.587.218 1.087.622 1.5 1.21.42.59.63 1.188.63 1.98 0 .812-.21 1.397-.63 1.976-.418.578-.897.974-1.436 1.187-.533.213-1.295.32-2.286.32h-5.65m4.768-2c.75 0 1.28-.05 1.584-.12.305-.077.57-.247.792-.51.23-.26.343-.472.343-.854 0-.557-.2-.868-.596-1.12-.4-.255-1.07-.397-2.02-.397H10v3"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="bold-cyrl-te">
<path id="te" d="M11 18V8H7V6h11v2h-4v10"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="bold-cyrl-te">
+ <path id="te" d="M11 18V8H7V6h11v2h-4v10"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="bold-cyrl-zhe">
<path id="cyrl-zhe" d="M13 6v5.154c.328-.033.537-.18.705-.447.168-.266.4-.873.698-1.82.39-1.242.79-2.034 1.197-2.375.403-.336 1.075-.504 2.014-.504L18 6v1.78l-.386-.008c-.4 0-.69.062-.878.187-.186.112-.337.3-.452.55-.115.25-.286.76-.512 1.533-.12.41-.25.755-.392 1.032-.137.275-.383.536-.738.78.44.156.8.465 1.084.926.288.455.603 1.103.944 1.943L18 18h-2.314l-1.17-3.08-.113-.253-.24-.56c-.247-.57-.45-.933-.61-1.09A.726.726 0 0 0 13 12.78V18h-2v-5.22c-.226 0-.382.077-.546.23-.164.15-.368.517-.612 1.097l-.246.56-.113.253L8.313 18H6l1.33-3.267c.327-.808.635-1.447.923-1.92.293-.476.663-.793 1.11-.95-.355-.244-.603-.5-.745-.772a6.357 6.357 0 0 1-.392-1.04c-.222-.76-.39-1.26-.505-1.52-.11-.25-.26-.44-.45-.57-.18-.12-.49-.18-.912-.18H6V6l.386.008c.953 0 1.63.17 2.034.512.4.347.79 1.136 1.177 2.366.3.954.534 1.564.698 1.83.168.26.377.405.705.438V6.002"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="bold-cyrl-zhe">
+ <path id="cyrl-zhe" d="M13 6v5.154c.328-.033.537-.18.705-.447.168-.266.4-.873.698-1.82.39-1.242.79-2.034 1.197-2.375.403-.336 1.075-.504 2.014-.504L18 6v1.78l-.386-.008c-.4 0-.69.062-.878.187-.186.112-.337.3-.452.55-.115.25-.286.76-.512 1.533-.12.41-.25.755-.392 1.032-.137.275-.383.536-.738.78.44.156.8.465 1.084.926.288.455.603 1.103.944 1.943L18 18h-2.314l-1.17-3.08-.113-.253-.24-.56c-.247-.57-.45-.933-.61-1.09A.726.726 0 0 0 13 12.78V18h-2v-5.22c-.226 0-.382.077-.546.23-.164.15-.368.517-.612 1.097l-.246.56-.113.253L8.313 18H6l1.33-3.267c.327-.808.635-1.447.923-1.92.293-.476.663-.793 1.11-.95-.355-.244-.603-.5-.745-.772a6.357 6.357 0 0 1-.392-1.04c-.222-.76-.39-1.26-.505-1.52-.11-.25-.26-.44-.45-.57-.18-.12-.49-.18-.912-.18H6V6l.386.008c.953 0 1.63.17 2.034.512.4.347.79 1.136 1.177 2.366.3.954.534 1.564.698 1.83.168.26.377.405.705.438V6.002"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="bold-f">
<path id="f" d="M16 8V6H8v12h3v-5h4v-2h-4V8z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="bold-f">
+ <path id="f" d="M16 8V6H8v12h3v-5h4v-2h-4V8z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="bold-g">
<path id="g" d="M12 14v-2h5v4.203c-.497.475-1.22.894-2.166 1.26A7.994 7.994 0 0 1 11.97 18c-1.23 0-2.303-.253-3.217-.76a4.908 4.908 0 0 1-2.062-2.185A7.008 7.008 0 0 1 6 11.96c0-1.208.26-2.282.77-3.222.518-.94 1.27-1.66 2.26-2.16.754-.386 1.693-.58 2.816-.58 1.46 0 2.6.304 3.418.91.825.603 1.354 1.436 1.59 2.502l-2.36.435a2.433 2.433 0 0 0-.94-1.346c-.454-.34-1.022-.5-1.707-.5-1.038 0-1.864.32-2.48.97-.61.65-.914 1.61-.914 2.89 0 1.375.31 2.41.93 3.1.62.687 1.434 1.03 2.44 1.03.497 0 .995-.095 1.49-.285.505-.196 1.334-.57 1.69-.846v-.868"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="bold-g">
+ <path id="g" d="M12 14v-2h5v4.203c-.497.475-1.22.894-2.166 1.26A7.994 7.994 0 0 1 11.97 18c-1.23 0-2.303-.253-3.217-.76a4.908 4.908 0 0 1-2.062-2.185A7.008 7.008 0 0 1 6 11.96c0-1.208.26-2.282.77-3.222.518-.94 1.27-1.66 2.26-2.16.754-.386 1.693-.58 2.816-.58 1.46 0 2.6.304 3.418.91.825.603 1.354 1.436 1.59 2.502l-2.36.435a2.433 2.433 0 0 0-.94-1.346c-.454-.34-1.022-.5-1.707-.5-1.038 0-1.864.32-2.48.97-.61.65-.914 1.61-.914 2.89 0 1.375.31 2.41.93 3.1.62.687 1.434 1.03 2.44 1.03.497 0 .995-.095 1.49-.285.505-.196 1.334-.57 1.69-.846v-.868"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="bold-geor-man">
<path id="geor-man" d="M13.832 14.06c0-1.714-.394-2.572-1.182-2.572-.868 0-1.302.78-1.302 2.338-.01 1.624.42 2.436 1.295 2.436.793 0 1.19-.734 1.19-2.2m2.167 0C16 16.686 14.884 18 12.65 18 10.218 18 9 16.614 9 13.84c0-2.737 1.217-4.105 3.65-4.105.842 0 1.183.63 1.183.63v-1.58c0-.788-.45-1.183-1.347-1.183-.572 0-.858.374-.858 1.123h-2.34C9.29 6.908 10.35 6 12.462 6 14.83 6 16.01 6.946 16 8.84"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="bold-geor-man">
+ <path id="geor-man" d="M13.832 14.06c0-1.714-.394-2.572-1.182-2.572-.868 0-1.302.78-1.302 2.338-.01 1.624.42 2.436 1.295 2.436.793 0 1.19-.734 1.19-2.2m2.167 0C16 16.686 14.884 18 12.65 18 10.218 18 9 16.614 9 13.84c0-2.737 1.217-4.105 3.65-4.105.842 0 1.183.63 1.183.63v-1.58c0-.788-.45-1.183-1.347-1.183-.572 0-.858.374-.858 1.123h-2.34C9.29 6.908 10.35 6 12.462 6 14.83 6 16.01 6.946 16 8.84"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="bold-l">
<path id="l" d="M8 18V6h3v10h5v2"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="bold-l">
+ <path id="l" d="M8 18V6h3v10h5v2"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="bold-n">
<path id="n" d="M7 18V6h3l4 8V6h3v12h-3l-4-8v8H7"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="bold-n">
+ <path id="n" d="M7 18V6h3l4 8V6h3v12h-3l-4-8v8H7"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="bold-v">
<path id="v" d="M10.5 18L6 6h3l3 8 3-8h3l-4.5 12"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="bold-v">
+ <path id="v" d="M10.5 18L6 6h3l3 8 3-8h3l-4.5 12"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M15 7c-1.7 0-3 1.3-3 3 0-1.7-1.3-3-3-3H3v13h6c1.7 0 3 1 3 2 0-1 1.3-2 3-2h6V7h-6zm5 12h-5c-1.7 0-2 .4-2 .4v-8.9C13 9.1 14.1 8 15.5 8H20v11z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M15 7c-1.7 0-3 1.3-3 3 0-1.7-1.3-3-3-3H3v13h6c1.7 0 3 1 3 2 0-1 1.3-2 3-2h6V7h-6zm5 12h-5c-1.7 0-2 .4-2 .4v-8.9C13 9.1 14.1 8 15.5 8H20v11z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M9 7c1.7 0 3 1.3 3 3 0-1.7 1.3-3 3-3h6v13h-6c-1.7 0-3 1-3 2 0-1-1.3-2-3-2H3V7h6zM4 19h5c1.7 0 2 .4 2 .4v-8.9C11 9.1 9.9 8 8.5 8H4v11z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M9 7c1.7 0 3 1.3 3 3 0-1.7 1.3-3 3-3h6v13h-6c-1.7 0-3 1-3 2 0-1-1.3-2-3-2H3V7h6zM4 19h5c1.7 0 2 .4 2 .4v-8.9C11 9.1 9.9 8 8.5 8H4v11z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M15 5H8c-1.1 0-2 .9-2 2v3h3v11l4-3 4 3V7c0-1.1-.9-2-2-2zM9 9H7V7c0-.6.4-1 1-1h1v3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M15 5H8c-1.1 0-2 .9-2 2v3h3v11l4-3 4 3V7c0-1.1-.9-2-2-2zM9 9H7V7c0-.6.4-1 1-1h1v3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M8 5h7c1.1 0 2 .9 2 2v3h-3v11l-4-3-4 3V7c0-1.1.9-2 2-2zm6 4h2V7c0-.6-.4-1-1-1h-1v3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M8 5h7c1.1 0 2 .9 2 2v3h-3v11l-4-3-4 3V7c0-1.1.9-2 2-2zm6 4h2V7c0-.6-.4-1-1-1h-1v3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M3 6v11c0 1.7 1.3 3 3 3h15V6H3zm2.5 1C6.3 7 7 7.7 7 8.5S6.3 10 5.5 10 4 9.3 4 8.5 4.7 7 5.5 7zM20 19H6c-1.1 0-2-.9-2-2v-6h16v8z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M3 6v11c0 1.7 1.3 3 3 3h15V6H3zm2.5 1C6.3 7 7 7.7 7 8.5S6.3 10 5.5 10 4 9.3 4 8.5 4.7 7 5.5 7zM20 19H6c-1.1 0-2-.9-2-2v-6h16v8z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M21 6v11c0 1.7-1.3 3-3 3H3V6h18zm-2.5 1c-.8 0-1.5.7-1.5 1.5s.7 1.5 1.5 1.5S20 9.3 20 8.5 19.3 7 18.5 7zM4 19h14c1.1 0 2-.9 2-2v-6H4v8z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M21 6v11c0 1.7-1.3 3-3 3H3V6h18zm-2.5 1c-.8 0-1.5.7-1.5 1.5s.7 1.5 1.5 1.5S20 9.3 20 8.5 19.3 7 18.5 7zM4 19h14c1.1 0 2-.9 2-2v-6H4v8z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M4 5v10c0 1.7 1.3 3 3 3h14V8c0-1.7-1.3-3-3-3H4zm2 1a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm4 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm4 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm4 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2zM5 9h3v2H5V9zm4 0h3v2H9V9zm4 0h3v2h-3V9zm4 0h3v2h-3V9zM5 12h3v2H5v-2zm4 0h3v2H9v-2zm4 0h3v2h-3v-2zm4 0h3v2h-3v-2zM5 15h3v2H7c-1.195 0-2-.805-2-2zm4 0h3v2H9v-2zm4 0h3v2h-3v-2zm4 0h3v2h-3v-2z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M4 5v10c0 1.7 1.3 3 3 3h14V8c0-1.7-1.3-3-3-3H4zm2 1a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm4 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm4 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm4 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2zM5 9h3v2H5V9zm4 0h3v2H9V9zm4 0h3v2h-3V9zm4 0h3v2h-3V9zM5 12h3v2H5v-2zm4 0h3v2H9v-2zm4 0h3v2h-3v-2zm4 0h3v2h-3v-2zM5 15h3v2H7c-1.195 0-2-.805-2-2zm4 0h3v2H9v-2zm4 0h3v2h-3v-2zm4 0h3v2h-3v-2z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M21 5v10c0 1.7-1.3 3-3 3H4V8c0-1.7 1.3-3 3-3h14zm-2 1a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm-4 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm-4 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2zM7 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm13 3h-3v2h3V9zm-4 0h-3v2h3V9zm-4 0H9v2h3V9zM8 9H5v2h3V9zm12 3h-3v2h3v-2zm-4 0h-3v2h3v-2zm-4 0H9v2h3v-2zm-4 0H5v2h3v-2zm12 3h-3v2h1c1.195 0 2-.805 2-2zm-4 0h-3v2h3v-2zm-4 0H9v2h3v-2zm-4 0H5v2h3v-2z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M21 5v10c0 1.7-1.3 3-3 3H4V8c0-1.7 1.3-3 3-3h14zm-2 1a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm-4 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm-4 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2zM7 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm13 3h-3v2h3V9zm-4 0h-3v2h3V9zm-4 0H9v2h3V9zM8 9H5v2h3V9zm12 3h-3v2h3v-2zm-4 0h-3v2h3v-2zm-4 0H9v2h3v-2zm-4 0H5v2h3v-2zm12 3h-3v2h1c1.195 0 2-.805 2-2zm-4 0h-3v2h3v-2zm-4 0H9v2h3v-2zm-4 0H5v2h3v-2z"/>
+</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: #d11d13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</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>
--- /dev/null
+<?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: #36c }</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>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M7 13.1l8.9 8.9c.8-.8.8-2 0-2.8l-6.1-6.1 6-6.1c.8-.8.8-2 0-2.8L7 13.1z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M7 13.1l8.9 8.9c.8-.8.8-2 0-2.8l-6.1-6.1 6-6.1c.8-.8.8-2 0-2.8L7 13.1z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M16.5 13.1L7.6 22c-.8-.8-.8-2 0-2.8l6.1-6.1-6-6.1c-.8-.8-.8-2 0-2.8l8.8 8.9z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M16.5 13.1L7.6 22c-.8-.8-.8-2 0-2.8l6.1-6.1-6-6.1c-.8-.8-.8-2 0-2.8l8.8 8.9z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M12 16l8.9-8.9c-.8-.8-2-.8-2.8 0L12 13.2l-6.1-6c-.8-.8-2-.8-2.8 0L12 16z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M12 16l8.9-8.9c-.8-.8-2-.8-2.8 0L12 13.2l-6.1-6c-.8-.8-2-.8-2.8 0L12 16z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M12 6.5l8.9 8.9c-.8.8-2 .8-2.8 0L12 9.3l-6.1 6c-.8.8-2 .8-2.8 0L12 6.5z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M12 6.5l8.9 8.9c-.8.8-2 .8-2.8 0L12 9.3l-6.1 6c-.8.8-2 .8-2.8 0L12 6.5z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="regular-expression">
<path id="upper-case" d="M7.53 7L4 17h2.063l.72-2.406h3.624l.72 2.406h2.062L9.65 7h-2.12zm1.064 1.53L9.938 13H7.25l1.344-4.47z"/>
<path id="lower-case" d="M18.55 17l-.184-1.035h-.055c-.35.44-.71.747-1.08.92-.37.167-.85.25-1.44.25-.564 0-.955-.208-1.377-.625-.42-.418-.627-1.012-.627-1.784 0-.808.283-1.403.846-1.784.568-.386 1.193-.607 2.208-.64l1.322-.04v-.335c0-.772-.396-1.158-1.187-1.158-.61 0-1.325.18-2.147.55l-.688-1.4c.877-.46 1.85-.69 2.916-.69 1.024 0 1.59.22 2.134.662.545.445.818 1.12.818 2.03V17h-1.45m-.394-3.527l-.802.027c-.604.018-1.054.127-1.35.327-.294.2-.442.504-.442.912 0 .58.336.87 1.008.87.48 0 .865-.137 1.152-.414.29-.277.436-.645.436-1.103v-.627"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="regular-expression">
+ <path id="upper-case" d="M7.53 7L4 17h2.063l.72-2.406h3.624l.72 2.406h2.062L9.65 7h-2.12zm1.064 1.53L9.938 13H7.25l1.344-4.47z"/>
+ <path id="lower-case" d="M18.55 17l-.184-1.035h-.055c-.35.44-.71.747-1.08.92-.37.167-.85.25-1.44.25-.564 0-.955-.208-1.377-.625-.42-.418-.627-1.012-.627-1.784 0-.808.283-1.403.846-1.784.568-.386 1.193-.607 2.208-.64l1.322-.04v-.335c0-.772-.396-1.158-1.187-1.158-.61 0-1.325.18-2.147.55l-.688-1.4c.877-.46 1.85-.69 2.916-.69 1.024 0 1.59.22 2.134.662.545.445.818 1.12.818 2.03V17h-1.45m-.394-3.527l-.802.027c-.604.018-1.054.127-1.35.327-.294.2-.442.504-.442.912 0 .58.336.87 1.008.87.48 0 .865-.137 1.152-.414.29-.277.436-.645.436-1.103v-.627"/>
+ </g>
+</svg>
+++ /dev/null
-<?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: #00af89 }</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: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</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: #d11d13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</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: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</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>
+++ /dev/null
-<?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: #00af89 }</style>
- <circle cx="12" cy="12" r="6"/>
-</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: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
<circle cx="12" cy="12" r="6"/>
</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<circle cx="12" cy="12" r="6"/>
</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: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
<circle cx="12" cy="12" r="6"/>
</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M7 12h9v-1H7v1zm0 2h9v-1H7v1zm0 2h9v-1H7v1zm4-9H7v1h4V7zm0 2H7v1h4V9zm0-4H7v1h4V5zm5-2h2v16H8c-1.7 0-3-1.3-3-3V3h8v7l1.5-2 1.5 2V3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M7 12h9v-1H7v1zm0 2h9v-1H7v1zm0 2h9v-1H7v1zm4-9H7v1h4V7zm0 2H7v1h4V9zm0-4H7v1h4V5zm5-2h2v16H8c-1.7 0-3-1.3-3-3V3h8v7l1.5-2 1.5 2V3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M16 12H7v-1h9v1zm0 2H7v-1h9v1zm0 2H7v-1h9v1zm-4-9h4v1h-4V7zm0 2h4v1h-4V9zm0-4h4v1h-4V5zM7 3H5v16h10c1.7 0 3-1.3 3-3V3h-8v7L8.5 8 7 10V3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M16 12H7v-1h9v1zm0 2H7v-1h9v1zm0 2H7v-1h9v1zm-4-9h4v1h-4V7zm0 2h4v1h-4V9zm0-4h4v1h-4V5zM7 3H5v16h10c1.7 0 3-1.3 3-3V3h-8v7L8.5 8 7 10V3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="clear">
<path id="circle-with-cross" d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm4 11l-1 1-3-3-3 3-1-1 3-3-3-3 1-1 3 3 3-3 1 1-3 3 3 3z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="clear">
+ <path id="circle-with-cross" d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm4 11l-1 1-3-3-3 3-1-1 3-3-3-3 1-1 3 3 3-3 1 1-3 3 3 3z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3 12l-4-3V8h2v5l1.7 1.2c1.3.9 1 1.9.3 2.8z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3 12l-4-3V8h2v5l1.7 1.2c1.3.9 1 1.9.3 2.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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="close">
<path id="cross" d="M17.4 8.1c.8-.8.8-2 0-2.8L12 10.8 7.4 6.2 6 7.6l4.6 4.6-4 4c-.8.8-.8 2 0 2.8l5.4-5.4 4.6 4.6 1.4-1.4-4.6-4.6z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="close">
+ <path id="cross" d="M17.4 8.1c.8-.8.8-2 0-2.8L12 10.8 7.4 6.2 6 7.6l4.6 4.6-4 4c-.8.8-.8 2 0 2.8l5.4-5.4 4.6 4.6 1.4-1.4-4.6-4.6z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="close">
<path id="cross" d="M6.6 8.1c-.8-.8-.8-2 0-2.8l5.4 5.5 4.6-4.6L18 7.6l-4.6 4.6 4 4c.8.8.8 2 0 2.8L12 13.6l-4.6 4.6L6 16.8l4.6-4.6z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="close">
+ <path id="cross" d="M6.6 8.1c-.8-.8-.8-2 0-2.8l5.4 5.5 4.6-4.6L18 7.6l-4.6 4.6 4 4c.8.8.8 2 0 2.8L12 13.6l-4.6 4.6L6 16.8l4.6-4.6z"/>
+ </g>
+</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="code">
<path id="left-bracket" d="M4 12v-1h1c1 0 1 0 1-1V7.614c0-.514.024-.896.073-1.142.054-.252.14-.463.257-.633.204-.28.473-.48.808-.59.335-.11.872-.25 1.835-.25H10v1h-.752c-.457 0-.77.19-.936.406-.167.216-.312.446-.312 1.07v1.856c0 .73-.04 1.18-.244 1.493-.2.307-.562.53-1.09.667.535.155.9.385 1.096.688.2.31.238.76.238 1.49v1.86c0 .62.145.85.312 1.06.166.22.48.41.936.41H10v1H8.973c-.963 0-1.5-.133-1.835-.248a1.578 1.578 0 0 1-.808-.59 1.68 1.68 0 0 1-.257-.626C6.023 16.283 6 15.9 6 15.386V13c0-1 0-1-1-1H4z"/>
<use transform="matrix(-1 0 0 1 24 0)" id="right-bracket" width="24" height="24" xlink:href="#left-bracket"/>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+ <g id="code">
+ <path id="left-bracket" d="M4 12v-1h1c1 0 1 0 1-1V7.614c0-.514.024-.896.073-1.142.054-.252.14-.463.257-.633.204-.28.473-.48.808-.59.335-.11.872-.25 1.835-.25H10v1h-.752c-.457 0-.77.19-.936.406-.167.216-.312.446-.312 1.07v1.856c0 .73-.04 1.18-.244 1.493-.2.307-.562.53-1.09.667.535.155.9.385 1.096.688.2.31.238.76.238 1.49v1.86c0 .62.145.85.312 1.06.166.22.48.41.936.41H10v1H8.973c-.963 0-1.5-.133-1.835-.248a1.578 1.578 0 0 1-.808-.59 1.68 1.68 0 0 1-.257-.626C6.023 16.283 6 15.9 6 15.386V13c0-1 0-1-1-1H4z"/>
+ <use transform="matrix(-1 0 0 1 24 0)" id="right-bracket" width="24" height="24" xlink:href="#left-bracket"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="collapse">
<path id="arrow" d="M6.697 15.714L12 10.412l5.303 5.302 1.414-1.414L12 7.583 5.283 14.3z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="collapse">
+ <path id="arrow" d="M6.697 15.714L12 10.412l5.303 5.302 1.414-1.414L12 7.583 5.283 14.3z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="comment">
<path id="speech-bubble" d="M15 6H9a3 3 0 0 0-3 3v4a3 3 0 0 0 3 3v3l3-3h3a3 3 0 0 0 3-3V9a3 3 0 0 0-3-3z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="comment">
+ <path id="speech-bubble" d="M15 6H9a3 3 0 0 0-3 3v4a3 3 0 0 0 3 3v3l3-3h3a3 3 0 0 0 3-3V9a3 3 0 0 0-3-3z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M16 5H4v12c0 1.6 1.3 3 3 3h12V8c0-1.7-1.4-3-3-3zM7.5 17c-.8 0-1.5-.7-1.5-1.5S6.7 14 7.5 14s1.5.7 1.5 1.5S8.3 17 7.5 17zm0-6C6.7 11 6 10.3 6 9.5S6.7 8 7.5 8 9 8.7 9 9.5 8.3 11 7.5 11zm4 3c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5 1.5.7 1.5 1.5-.7 1.5-1.5 1.5zm4 3c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5 1.5.7 1.5 1.5-.7 1.5-1.5 1.5zm0-6c-.8 0-1.5-.7-1.5-1.5S14.7 8 15.5 8s1.5.7 1.5 1.5-.7 1.5-1.5 1.5z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M16 5H4v12c0 1.6 1.3 3 3 3h12V8c0-1.7-1.4-3-3-3zM7.5 17c-.8 0-1.5-.7-1.5-1.5S6.7 14 7.5 14s1.5.7 1.5 1.5S8.3 17 7.5 17zm0-6C6.7 11 6 10.3 6 9.5S6.7 8 7.5 8 9 8.7 9 9.5 8.3 11 7.5 11zm4 3c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5 1.5.7 1.5 1.5-.7 1.5-1.5 1.5zm4 3c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5 1.5.7 1.5 1.5-.7 1.5-1.5 1.5zm0-6c-.8 0-1.5-.7-1.5-1.5S14.7 8 15.5 8s1.5.7 1.5 1.5-.7 1.5-1.5 1.5z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M7 5h12v12c0 1.6-1.3 3-3 3H4V8c0-1.7 1.4-3 3-3zm8.5 12c.8 0 1.5-.7 1.5-1.5s-.7-1.5-1.5-1.5-1.5.7-1.5 1.5.7 1.5 1.5 1.5zm0-6c.8 0 1.5-.7 1.5-1.5S16.3 8 15.5 8 14 8.7 14 9.5s.7 1.5 1.5 1.5zm-4 3c.8 0 1.5-.7 1.5-1.5s-.7-1.5-1.5-1.5-1.5.7-1.5 1.5.7 1.5 1.5 1.5zm-4 3c.8 0 1.5-.7 1.5-1.5S8.3 14 7.5 14 6 14.7 6 15.5 6.7 17 7.5 17zm0-6c.8 0 1.5-.7 1.5-1.5S8.3 8 7.5 8 6 8.7 6 9.5 6.7 11 7.5 11z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M7 5h12v12c0 1.6-1.3 3-3 3H4V8c0-1.7 1.4-3 3-3zm8.5 12c.8 0 1.5-.7 1.5-1.5s-.7-1.5-1.5-1.5-1.5.7-1.5 1.5.7 1.5 1.5 1.5zm0-6c.8 0 1.5-.7 1.5-1.5S16.3 8 15.5 8 14 8.7 14 9.5s.7 1.5 1.5 1.5zm-4 3c.8 0 1.5-.7 1.5-1.5s-.7-1.5-1.5-1.5-1.5.7-1.5 1.5.7 1.5 1.5 1.5zm-4 3c.8 0 1.5-.7 1.5-1.5S8.3 14 7.5 14 6 14.7 6 15.5 6.7 17 7.5 17zm0-6c.8 0 1.5-.7 1.5-1.5S8.3 8 7.5 8 6 8.7 6 9.5 6.7 11 7.5 11z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M12 18l8-10H4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M12 18l8-10H4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M16 11h-3V4c-1.7 0-3 1.3-3 3v4H7l4.5 5 4.5-5zm1 2v5H7c-.6 0-1-.4-1-1v-4H4v4c0 1.9 1.3 3 3 3h12v-7h-2z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M16 11h-3V4c-1.7 0-3 1.3-3 3v4H7l4.5 5 4.5-5zm1 2v5H7c-.6 0-1-.4-1-1v-4H4v4c0 1.9 1.3 3 3 3h12v-7h-2z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M7 11h3V4c1.7 0 3 1.3 3 3v4h3l-4.5 5L7 11zm-1 2v5h10c.6 0 1-.4 1-1v-4h2v4c0 1.9-1.3 3-3 3H4v-7h2z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M7 11h3V4c1.7 0 3 1.3 3 3v4h3l-4.5 5L7 11zm-1 2v5h10c.6 0 1-.4 1-1v-4h2v4c0 1.9-1.3 3-3 3H4v-7h2z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M17 2L5 14l-1 5 5-1L21 6c0-2-2-4-4-4zM7.2 15.5c-.3-.3-.7-.6-1-.8C8.5 12.4 17.5 3.3 17.5 3.3c.4.1.7.3 1 .7L7.2 15.5z"/>
</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: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
<path d="M17 2L5 14l-1 5 5-1L21 6c0-2-2-4-4-4zM7.2 15.5c-.3-.3-.7-.6-1-.8C8.5 12.4 17.5 3.3 17.5 3.3c.4.1.7.3 1 .7L7.2 15.5z"/>
</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M8 2l12 12 1 5-5-1L4 6c0-2 2-4 4-4zm9.8 13.5c.3-.3.7-.6 1-.8C16.5 12.4 7.5 3.3 7.5 3.3c-.4.1-.7.3-1 .7l11.3 11.5z"/>
</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: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
<path d="M8 2l12 12 1 5-5-1L4 6c0-2 2-4 4-4zm9.8 13.5c.3-.3.7-.6 1-.8C16.5 12.4 7.5 3.3 7.5 3.3c-.4.1-.7.3-1 .7l11.3 11.5z"/>
</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M21 4V3s0-3-3-3-3 3-3 3v1h-1v6h8V4zm-1.5 0h-3V3s0-1.5 1.5-1.5c1.48.06 1.5 1.5 1.5 1.5zM13 9.6l-6.8 6.9c-.3-.3-.7-.6-1-.8 1.4-1.4 5-5 7.8-7.9V6l-9 9-1 5 5-1 8-8h-3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M21 4V3s0-3-3-3-3 3-3 3v1h-1v6h8V4zm-1.5 0h-3V3s0-1.5 1.5-1.5c1.48.06 1.5 1.5 1.5 1.5zM13 9.6l-6.8 6.9c-.3-.3-.7-.6-1-.8 1.4-1.4 5-5 7.8-7.9V6l-9 9-1 5 5-1 8-8h-3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M4 4V3s0-3 3-3 3 3 3 3v1h1v6H3V4zm1.5 0h3V3s0-1.5-1.5-1.5C5.52 1.56 5.5 3 5.5 3zM12 9.6l6.8 6.9c.3-.3.7-.6 1-.8-1.4-1.4-5-5-7.8-7.9V6l9 9 1 5-5-1-8-8h3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M4 4V3s0-3 3-3 3 3 3 3v1h1v6H3V4zm1.5 0h3V3s0-1.5-1.5-1.5C5.52 1.56 5.5 3 5.5 3zM12 9.6l6.8 6.9c.3-.3.7-.6 1-.8-1.4-1.4-5-5-7.8-7.9V6l9 9 1 5-5-1-8-8h3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M14.9 2.8c.9 0 1.8.2 2.7.6.9.4 1.6.9 1.9 1.6-2.8.1-5 1.1-6.6 3.1l1.3 2-6.7-.3L8 3l1.7 2c1.8-1.5 3.5-2.2 5.2-2.2z"/>
<path d="M15.2 11.1l-2.6-.1-5.4 5.5c-.3-.3-.7-.6-1-.8.9-.9 2.8-2.8 4.7-4.8H9.1L5 15l-1 5 5-1 7.8-7.8-1.6-.1zM20.6 6c-1.7 0-3.2.5-4.4 1.4l-.9.9.8 1.3.9 1.4 4-4c0-.3-.1-.7-.2-1h-.2z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M14.9 2.8c.9 0 1.8.2 2.7.6.9.4 1.6.9 1.9 1.6-2.8.1-5 1.1-6.6 3.1l1.3 2-6.7-.3L8 3l1.7 2c1.8-1.5 3.5-2.2 5.2-2.2z"/>
+ <path d="M15.2 11.1l-2.6-.1-5.4 5.5c-.3-.3-.7-.6-1-.8.9-.9 2.8-2.8 4.7-4.8H9.1L5 15l-1 5 5-1 7.8-7.8-1.6-.1zM20.6 6c-1.7 0-3.2.5-4.4 1.4l-.9.9.8 1.3.9 1.4 4-4c0-.3-.1-.7-.2-1h-.2z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M10.1 2.8c-.9 0-1.8.2-2.7.6-.9.4-1.6.9-1.9 1.6 2.8.1 5 1.1 6.6 3.1l-1.3 2 6.7-.3L17 3l-1.7 2c-1.8-1.5-3.5-2.2-5.2-2.2z"/>
<path d="M9.8 11.1l2.6-.1 5.4 5.5c.3-.3.7-.6 1-.8-.9-.9-2.8-2.8-4.7-4.8h1.8L20 15l1 5-5-1-7.8-7.8 1.6-.1zM4.4 6c1.7 0 3.2.5 4.4 1.4l.9.9-.8 1.3L8 11 4 7c0-.3.1-.7.2-1h.2z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M10.1 2.8c-.9 0-1.8.2-2.7.6-.9.4-1.6.9-1.9 1.6 2.8.1 5 1.1 6.6 3.1l-1.3 2 6.7-.3L17 3l-1.7 2c-1.8-1.5-3.5-2.2-5.2-2.2z"/>
+ <path d="M9.8 11.1l2.6-.1 5.4 5.5c.3-.3.7-.6 1-.8-.9-.9-2.8-2.8-4.7-4.8h1.8L20 15l1 5-5-1-7.8-7.8 1.6-.1zM4.4 6c1.7 0 3.2.5 4.4 1.4l.9.9-.8 1.3L8 11 4 7c0-.3.1-.7.2-1h.2z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M8 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4zM14 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4zM20 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M8 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4zM14 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4zM20 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="expand">
<path id="arrow" d="M17.303 8.283L12 13.586 6.697 8.283 5.283 9.697 12 16.414l6.717-6.717z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="expand">
+ <path id="arrow" d="M17.303 8.283L12 13.586 6.697 8.283 5.283 9.697 12 16.414l6.717-6.717z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="external">
<path id="box" d="M4 4h6v2H6v12h12v-4h2v6H4z"/>
<path id="arrow" d="M12.42 4H20v7.58l-2.84-2.846L12.892 13 11 11.106l4.264-4.266z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="external">
+ <path id="box" d="M4 4h6v2H6v12h12v-4h2v6H4z"/>
+ <path id="arrow" d="M12.42 4H20v7.58l-2.84-2.846L12.892 13 11 11.106l4.264-4.266z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="external">
<path id="box" d="M20 4h-6v2h4v12H6v-4H4v6h16z"/>
<path id="arrow" d="M11.58 4H4v7.58l2.84-2.846L11.108 13 13 11.106 8.736 6.84z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="external">
+ <path id="box" d="M20 4h-6v2h4v12H6v-4H4v6h16z"/>
+ <path id="arrow" d="M11.58 4H4v7.58l2.84-2.846L11.108 13 13 11.106 8.736 6.84z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M12 8c-5 0-11 6-11 6s6 6 11 6 11-6 11-6-6-6-11-6zm0 10c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
<circle cx="12" cy="14" r="2"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M12 8c-5 0-11 6-11 6s6 6 11 6 11-6 11-6-6-6-11-6zm0 10c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
+ <circle cx="12" cy="14" r="2"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M19.4 12.7c.7-.8 1.2-1.7 1.4-2.7h-1.6c-.9 2.5-3.9 4.4-7.7 4.6h-.1c-3.7-.2-6.8-2.1-7.7-4.6H2.2c.2 1 .8 1.9 1.4 2.7l-2 2 .7.7 2-2c.8.6 1.7 1.2 2.7 1.7l-1 2.8.9.3 1-2.8c1 .3 2 .6 3.1.6v3h1v-3c1.1-.1 2.2-.3 3.1-.6l1 2.8.9-.3-1-2.8c1-.4 1.9-1 2.6-1.7l2 2 .7-.7-1.9-2z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M19.4 12.7c.7-.8 1.2-1.7 1.4-2.7h-1.6c-.9 2.5-3.9 4.4-7.7 4.6h-.1c-3.7-.2-6.8-2.1-7.7-4.6H2.2c.2 1 .8 1.9 1.4 2.7l-2 2 .7.7 2-2c.8.6 1.7 1.2 2.7 1.7l-1 2.8.9.3 1-2.8c1 .3 2 .6 3.1.6v3h1v-3c1.1-.1 2.2-.3 3.1-.6l1 2.8.9-.3-1-2.8c1-.4 1.9-1 2.6-1.7l2 2 .7-.7-1.9-2z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="find">
<path id="magnifying-glass" d="M13.656 11c-1.92 0-3.5 1.548-3.5 3.47 0 1.92 1.58 3.5 3.5 3.5.75 0 1.432-.253 2-.657l.094.156 2.375 2.37c.19.19.534.15.78-.096s.315-.59.126-.78l-2.37-2.377-.185-.094a3.545 3.545 0 0 0 .655-2.03c0-1.92-1.55-3.47-3.47-3.47zm0 1.656a1.8 1.8 0 0 1 1.813 1.813 1.83 1.83 0 0 1-1.82 1.84c-1.01 0-1.844-.83-1.844-1.847s.832-1.814 1.844-1.814z"/>
<path id="text" d="M6 5v2h10V5H6zm0 3v2h11V8H6zm0 3v2h3.53a4.443 4.443 0 0 1 1.44-2H6zm0 3v2h3.53c-.177-.48-.28-.99-.28-1.53 0-.16.046-.315.063-.47H6z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="find">
+ <path id="magnifying-glass" d="M13.656 11c-1.92 0-3.5 1.548-3.5 3.47 0 1.92 1.58 3.5 3.5 3.5.75 0 1.432-.253 2-.657l.094.156 2.375 2.37c.19.19.534.15.78-.096s.315-.59.126-.78l-2.37-2.377-.185-.094a3.545 3.545 0 0 0 .655-2.03c0-1.92-1.55-3.47-3.47-3.47zm0 1.656a1.8 1.8 0 0 1 1.813 1.813 1.83 1.83 0 0 1-1.82 1.84c-1.01 0-1.844-.83-1.844-1.847s.832-1.814 1.844-1.814z"/>
+ <path id="text" d="M6 5v2h10V5H6zm0 3v2h11V8H6zm0 3v2h3.53a4.443 4.443 0 0 1 1.44-2H6zm0 3v2h3.53c-.177-.48-.28-.99-.28-1.53 0-.16.046-.315.063-.47H6z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="find">
<path id="magnifying-glass" d="M11.344 11c1.92 0 3.5 1.548 3.5 3.47 0 1.92-1.58 3.5-3.5 3.5-.75 0-1.432-.253-2-.657l-.094.156-2.375 2.37c-.19.19-.534.15-.78-.096s-.315-.59-.126-.78l2.37-2.377.185-.094a3.545 3.545 0 0 1-.655-2.03c0-1.92 1.55-3.47 3.47-3.47zm0 1.656A1.8 1.8 0 0 0 9.53 14.47c0 1.01.806 1.84 1.818 1.84 1.01 0 1.844-.83 1.844-1.845s-.832-1.814-1.844-1.814z"/>
<path id="text" d="M19 5v2H9V5zm0 3v2H8V8zm0 3v2h-3.53a4.443 4.443 0 0 0-1.44-2zm0 3v2h-3.53c.177-.48.28-.99.28-1.53 0-.16-.046-.315-.063-.47z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="find">
+ <path id="magnifying-glass" d="M11.344 11c1.92 0 3.5 1.548 3.5 3.47 0 1.92-1.58 3.5-3.5 3.5-.75 0-1.432-.253-2-.657l-.094.156-2.375 2.37c-.19.19-.534.15-.78-.096s-.315-.59-.126-.78l2.37-2.377.185-.094a3.545 3.545 0 0 1-.655-2.03c0-1.92 1.55-3.47 3.47-3.47zm0 1.656A1.8 1.8 0 0 0 9.53 14.47c0 1.01.806 1.84 1.818 1.84 1.01 0 1.844-.83 1.844-1.845s-.832-1.814-1.844-1.814z"/>
+ <path id="text" d="M19 5v2H9V5zm0 3v2H8V8zm0 3v2h-3.53a4.443 4.443 0 0 0-1.44-2zm0 3v2h-3.53c.177-.48.28-.99.28-1.53 0-.16-.046-.315-.063-.47z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M14 6.5V5c-1.4-1.5-5.2-1.2-6 0V4H7v15h1v-7c.8-.8 3.4-.9 5-.5V13c1.2 1.5 4.3 1.2 5 0V6c-.7.7-2.7.9-4 .5z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M14 6.5V5c-1.4-1.5-5.2-1.2-6 0V4H7v15h1v-7c.8-.8 3.4-.9 5-.5V13c1.2 1.5 4.3 1.2 5 0V6c-.7.7-2.7.9-4 .5z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M10.3 7.5V6c1.4-1.5 5.2-1.2 6 0V5h1v15h-1v-7c-.8-.8-3.4-.9-5-.5V14c-1.2 1.5-4.3 1.2-5 0V7c.7.7 2.7.9 4 .5z"/>
</svg>
\ No newline at end of file
--- /dev/null
+<?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: #36c }</style>
+ <path d="M10.3 7.5V6c1.4-1.5 5.2-1.2 6 0V5h1v15h-1v-7c-.8-.8-3.4-.9-5-.5V14c-1.2 1.5-4.3 1.2-5 0V7c.7.7 2.7.9 4 .5z"/>
+</svg>
\ No newline at end of file
<?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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M19.9 19.6l-16-16-1.1 1.1L6 7.9V20h1v-7c.6-.6 2-.8 3.4-.7l8.4 8.4 1.1-1.1zM17 14V7c-.7.7-2.7.9-4 .5V6c-1.2-1.3-3.9-1.3-5.4-.5l8.9 9c.3-.2.4-.3.5-.5z"/>
</svg>
\ No newline at end of file
--- /dev/null
+<?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: #36c }</style>
+ <path d="M19.9 19.6l-16-16-1.1 1.1L6 7.9V20h1v-7c.6-.6 2-.8 3.4-.7l8.4 8.4 1.1-1.1zM17 14V7c-.7.7-2.7.9-4 .5V6c-1.2-1.3-3.9-1.3-5.4-.5l8.9 9c.3-.2.4-.3.5-.5z"/>
+</svg>
\ No newline at end of file
<?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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M3.5 19.6l16-16 1.1 1.1-3.2 3.2V20h-1v-7c-.6-.6-2-.8-3.4-.7l-8.4 8.4-1.1-1.1zM6.3 14V7c.7.7 2.7.9 4 .5V6c1.2-1.3 3.9-1.3 5.4-.5l-8.9 9c-.3-.2-.4-.3-.5-.5z"/>
</svg>
\ No newline at end of file
--- /dev/null
+<?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: #36c }</style>
+ <path d="M3.5 19.6l16-16 1.1 1.1-3.2 3.2V20h-1v-7c-.6-.6-2-.8-3.4-.7l-8.4 8.4-1.1-1.1zM6.3 14V7c.7.7 2.7.9 4 .5V6c1.2-1.3 3.9-1.3 5.4-.5l-8.9 9c-.3-.2-.4-.3-.5-.5z"/>
+</svg>
\ No newline at end of file
<?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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M2 5v15h20V5H2zm15 11H8c-.6 0-1-.4-1-1V9h3l2 1h5v6z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M2 5v15h20V5H2zm15 11H8c-.6 0-1-.4-1-1V9h3l2 1h5v6z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M22 5v15H2V5h20zM7 16h9c.6 0 1-.4 1-1V9h-3l-2 1H7v6z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M22 5v15H2V5h20zM7 16h9c.6 0 1-.4 1-1V9h-3l-2 1H7v6z"/>
+</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path id="arrow" d="M6 6v4l1.28-1.28 2 2 1.423-1.44-2-2L10 6z"/>
<use transform="rotate(90 12 12)" xlink:href="#arrow"/>
<use transform="rotate(180 12 12)" xlink:href="#arrow"/>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+ <path id="arrow" d="M6 6v4l1.28-1.28 2 2 1.423-1.44-2-2L10 6z"/>
+ <use transform="rotate(90 12 12)" xlink:href="#arrow"/>
+ <use transform="rotate(180 12 12)" xlink:href="#arrow"/>
+ <use transform="rotate(-90 12 12)" xlink:href="#arrow"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M11 13L5 6h15l-6 7v7c-1.7 0-3-1.3-3-3v-4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M11 13L5 6h15l-6 7v7c-1.7 0-3-1.3-3-3v-4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M14 13l6-7H5l6 7v7c1.7 0 3-1.3 3-3v-4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M14 13l6-7H5l6 7v7c1.7 0 3-1.3 3-3v-4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M15 7c-2 0-3 2-3 2s-1-2-3-2c-2.5 0-4 2-4 4 0 4 5 5 7 8 2-3 7-4 7-8 0-2-1.5-4-4-4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M15 7c-2 0-3 2-3 2s-1-2-3-2c-2.5 0-4 2-4 4 0 4 5 5 7 8 2-3 7-4 7-8 0-2-1.5-4-4-4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="help">
<path id="circle" d="M12 2.085c-5.477 0-9.915 4.438-9.915 9.916 0 5.48 4.438 9.92 9.916 9.92 5.48 0 9.92-4.44 9.92-9.913 0-5.477-4.44-9.915-9.913-9.915zm.002 18a8.084 8.084 0 1 1 0-16.168 8.084 8.084 0 0 1 0 16.168z"/>
<g id="question-mark">
--- /dev/null
+<?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: #36c }</style>
+ <g id="help">
+ <path id="circle" d="M12 2.085c-5.477 0-9.915 4.438-9.915 9.916 0 5.48 4.438 9.92 9.916 9.92 5.48 0 9.92-4.44 9.92-9.913 0-5.477-4.44-9.915-9.913-9.915zm.002 18a8.084 8.084 0 1 1 0-16.168 8.084 8.084 0 0 1 0 16.168z"/>
+ <g id="question-mark">
+ <path id="top" d="M11.766 6.688c-2.5 0-3.22 2.188-3.22 2.188l1.412.854s.298-.79.9-1.23c.517-.374 1.626-.624 2.22.126.7.885-.17 1.587-1.078 2.72C11.047 12.53 11 15 11 15h1.97s.134-2.318 1.04-3.38c.603-.708 1.443-1.34 1.443-2.495s-1.187-2.437-3.687-2.437z"/>
+ <path id="bottom" d="M11 16h2v2h-2z"/>
+ </g>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="help">
<path id="circle" d="M12 2.085c5.477 0 9.915 4.438 9.915 9.916 0 5.48-4.438 9.92-9.916 9.92-5.48 0-9.92-4.44-9.92-9.913 0-5.477 4.44-9.915 9.913-9.915zm-.002 18a8.084 8.084 0 1 0 0-16.168 8.084 8.084 0 0 0 0 16.168z"/>
<g id="question-mark">
--- /dev/null
+<?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: #36c }</style>
+ <g id="help">
+ <path id="circle" d="M12 2.085c5.477 0 9.915 4.438 9.915 9.916 0 5.48-4.438 9.92-9.916 9.92-5.48 0-9.92-4.44-9.92-9.913 0-5.477 4.44-9.915 9.913-9.915zm-.002 18a8.084 8.084 0 1 0 0-16.168 8.084 8.084 0 0 0 0 16.168z"/>
+ <g id="question-mark">
+ <path id="top" d="M12.234 6.688c2.5 0 3.22 2.188 3.22 2.188l-1.412.854s-.298-.79-.9-1.23c-.517-.374-1.626-.624-2.22.126-.7.885.17 1.587 1.078 2.72C12.953 12.53 13 15 13 15h-1.97s-.134-2.318-1.04-3.38c-.603-.708-1.443-1.34-1.443-2.495 0-1.156 1.187-2.437 3.687-2.437z"/>
+ <path id="bottom" d="M13 16h-2v2h2z"/>
+ </g>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="history">
<path id="clock-hands" d="M17.26 15.076s-2.385-1.935-4.005-3.062c.72-2.397 1.702-6.56 1.702-6.56s-4.35 5.364-4.877 6.7c-.463 1.168 1.46 2.21 2.346 1.678 1.9.55 4.834 1.244 4.834 1.244z"/>
<path id="arrow" d="M12.086 2.085C6.608 2.085 2.17 6.523 2.17 12a9.86 9.86 0 0 0 1.3 4.9l-2.22 2.04h5.688v-5.22L4.87 15.616A7.982 7.982 0 0 1 4.004 12a8.084 8.084 0 0 1 16.167.004 8.08 8.08 0 0 1-8.08 8.085 7.975 7.975 0 0 1-3.21-.68L8.05 21.04a9.81 9.81 0 0 0 4.045.874C17.563 21.914 22 17.476 22 12c0-5.477-4.438-9.915-9.914-9.915z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="history">
+ <path id="clock-hands" d="M17.26 15.076s-2.385-1.935-4.005-3.062c.72-2.397 1.702-6.56 1.702-6.56s-4.35 5.364-4.877 6.7c-.463 1.168 1.46 2.21 2.346 1.678 1.9.55 4.834 1.244 4.834 1.244z"/>
+ <path id="arrow" d="M12.086 2.085C6.608 2.085 2.17 6.523 2.17 12a9.86 9.86 0 0 0 1.3 4.9l-2.22 2.04h5.688v-5.22L4.87 15.616A7.982 7.982 0 0 1 4.004 12a8.084 8.084 0 0 1 16.167.004 8.08 8.08 0 0 1-8.08 8.085 7.975 7.975 0 0 1-3.21-.68L8.05 21.04a9.81 9.81 0 0 0 4.045.874C17.563 21.914 22 17.476 22 12c0-5.477-4.438-9.915-9.914-9.915z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="image">
<path id="mountains" d="M18 17l-3-3-2 1-3-3-4 5zm2-11v13H4V6z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="image">
+ <path id="mountains" d="M18 17l-3-3-2 1-3-3-4 5zm2-11v13H4V6z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="image">
<path id="mountains" d="M6 17l3-3 2 1 3-3 4 5zM4 6v13h16V6z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="image">
+ <path id="mountains" d="M6 17l3-3 2 1 3-3 4 5zM4 6v13h16V6z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="imageAdd">
<path id="mountains" d="M16 17l-3-3-2 1-3-3-4 5zm-1-8v4h3v6H2V6h9v3z"/>
<path id="add" d="M22 6h-4V2h-2v4h-4v2h4v4h2V8h4z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="imageAdd">
+ <path id="mountains" d="M16 17l-3-3-2 1-3-3-4 5zm-1-8v4h3v6H2V6h9v3z"/>
+ <path id="add" d="M22 6h-4V2h-2v4h-4v2h4v4h2V8h4z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="imageAdd">
<path id="mountains" d="M8 17l3-3 2 1 3-3 4 5zm1-8v4H6v6h16V6h-9v3z"/>
<path id="add" d="M2 6h4V2h2v4h4v2H8v4H6V8H2z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="imageAdd">
+ <path id="mountains" d="M8 17l3-3 2 1 3-3 4 5zm1-8v4H6v6h16V6h-9v3z"/>
+ <path id="add" d="M2 6h4V2h2v4h4v2H8v4H6V8H2z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M2 4v14h2V6h15V4H2zm3 3v13h16V7H5zm6 6l3 3 2-1 3 3H7l4-5z" id="imageGallery"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M2 4v14h2V6h15V4H2zm3 3v13h16V7H5zm6 6l3 3 2-1 3 3H7l4-5z" id="imageGallery"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M21 4v14h-2V6H4V4h17zm-3 3v13H2V7h16zm-6 6l-3 3-2-1-3 3h12l-4-5z" id="imageGallery"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M21 4v14h-2V6H4V4h17zm-3 3v13H2V7h16zm-6 6l-3 3-2-1-3 3h12l-4-5z" id="imageGallery"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="imageAdd">
<path id="mountains" d="M18 17l-3-3-2 1-3-3-4 5zm2-5v7H4V6h8v6z"/>
<path id="lock" d="M18.5 5h-3V4s0-1.5 1.5-1.5c1.5.06 1.5 1.5 1.5 1.5zM20 5V4s0-3-3-3-3 3-3 3v1h-1v6h8V5z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="imageAdd">
+ <path id="mountains" d="M18 17l-3-3-2 1-3-3-4 5zm2-5v7H4V6h8v6z"/>
+ <path id="lock" d="M18.5 5h-3V4s0-1.5 1.5-1.5c1.5.06 1.5 1.5 1.5 1.5zM20 5V4s0-3-3-3-3 3-3 3v1h-1v6h8V5z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="imageAdd">
<path id="mountains" d="M7 17l3-3 2 1 3-3 4 5zm-2-5v7h16V6h-8v6z"/>
<path id="lock" d="M6.5 5h3V4s0-1.5-1.5-1.5C6.5 2.56 6.5 4 6.5 4zM5 5V4s0-3 3-3 3 3 3 3v1h1v6H4V5z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="imageAdd">
+ <path id="mountains" d="M7 17l3-3 2 1 3-3 4 5zm-2-5v7h16V6h-8v6z"/>
+ <path id="lock" d="M6.5 5h3V4s0-1.5-1.5-1.5C6.5 2.56 6.5 4 6.5 4zM5 5V4s0-3 3-3 3 3 3 3v1h1v6H4V5z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M10 8h9v2h-9V8zm0 3h9v2h-9v-2zm0 3h6v2h-6v-2zm11-8H3V4h18v2zm0 14H3v-2h18v2zM3 8v8l5-4-5-4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M10 8h9v2h-9V8zm0 3h9v2h-9v-2zm0 3h6v2h-6v-2zm11-8H3V4h18v2zm0 14H3v-2h18v2zM3 8v8l5-4-5-4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M14 8H5v2h9V8zm0 3H5v2h9v-2zm0 3H8v2h6v-2zM3 6h18V4H3v2zm0 14h18v-2H3v2zM21 8v8l-5-4 5-4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M14 8H5v2h9V8zm0 3H5v2h9v-2zm0 3H8v2h6v-2zM3 6h18V4H3v2zm0 14h18v-2H3v2zM21 8v8l-5-4 5-4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="info">
<path id="circled-i" d="M11.5 17a5.5 5.5 0 1 1 0-11 5.5 5.5 0 0 1 0 11zm0-12a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13zm.5 5v4h1v1h-3v-1h1v-3h-1v-1zm-1-2h1v1h-1z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="info">
+ <path id="circled-i" d="M11.5 17a5.5 5.5 0 1 1 0-11 5.5 5.5 0 0 1 0 11zm0-12a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13zm.5 5v4h1v1h-3v-1h1v-3h-1v-1zm-1-2h1v1h-1z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="italic-a">
<path id="a" d="M14.667 6h-1.372l-7 12H8l2.333-4h4L15 18h1.667l-2-12zm-3.75 7l2.527-4.333.723 4.333h-3.25z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="italic-a">
+ <path id="a" d="M14.667 6h-1.372l-7 12H8l2.333-4h4L15 18h1.667l-2-12zm-3.75 7l2.527-4.333.723 4.333h-3.25z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="italic-arab-keheh-jeem">
<path id="arab-keheh-jeem" d="M18.125 5.844c-1.695.555-3.297 1.162-4.594 1.938-.49.3-.77.712-.87 1.125a1.26 1.26 0 0 0 .065.78c.19.406.54.575.844.814l.093-.12.53.627c.14.165.345.514.47.94.138.462.08.724 0 1.124h-3.44c-.34 0-.592.007-.766-.02-.34-.053-.256-.21-.234-.34.33-.127.56-.173.934-.14.29-.495.593-.886.906-1.314-.98.037-1.877.015-2.687-.094-.346-.046-.698-.185-1.094-.155-.36.026-.77.24-1.03.72-.25.447-.436.838-.66 1.28l.75-.47c.23-.14.486-.226.72-.218.158.004.276.053.407.093-.234.204-.51.4-.72.56-.3.26-.704.69-.908 1-.403.617-.694 1.086-.875 1.78-.18.69.003 1.34.468 1.75.426.38.846.52 1.28.566.65.064 1.206.092 2-.19.658-.23 1.022-.552 1.5-.97-.882.11-1.816.09-2.53.033-.87-.07-1.268-.386-1.47-.596-.27-.283-.306-.64-.155-1.22a1.44 1.44 0 0 1 .25-.53c.17-.228.363-.435.593-.656.45-.436 1.01-.737 1.46-.94-.042.207-.104.444-.052.69.05.23.25.38.44.47.26.12.506.152.69.153 1.42.01 2.86 0 4.28 0 .246 0 .45-.163.593-.375.14-.21.25-.48.343-.845.13-.5.094-1.062-.094-1.625a4.812 4.812 0 0 0-.72-1.406c-.336-.444-.675-.83-1-1.22 1.256-.815 2.715-1.24 3.97-1.688.12-.452.222-.926.31-1.313zm-9.47 8.438c-.26.394-.583.69-.874 1 .38.286.75.556 1.1.813.336-.303.627-.674.876-.97-.39-.267-.77-.587-1.093-.843z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="italic-arab-keheh-jeem">
+ <path id="arab-keheh-jeem" d="M18.125 5.844c-1.695.555-3.297 1.162-4.594 1.938-.49.3-.77.712-.87 1.125a1.26 1.26 0 0 0 .065.78c.19.406.54.575.844.814l.093-.12.53.627c.14.165.345.514.47.94.138.462.08.724 0 1.124h-3.44c-.34 0-.592.007-.766-.02-.34-.053-.256-.21-.234-.34.33-.127.56-.173.934-.14.29-.495.593-.886.906-1.314-.98.037-1.877.015-2.687-.094-.346-.046-.698-.185-1.094-.155-.36.026-.77.24-1.03.72-.25.447-.436.838-.66 1.28l.75-.47c.23-.14.486-.226.72-.218.158.004.276.053.407.093-.234.204-.51.4-.72.56-.3.26-.704.69-.908 1-.403.617-.694 1.086-.875 1.78-.18.69.003 1.34.468 1.75.426.38.846.52 1.28.566.65.064 1.206.092 2-.19.658-.23 1.022-.552 1.5-.97-.882.11-1.816.09-2.53.033-.87-.07-1.268-.386-1.47-.596-.27-.283-.306-.64-.155-1.22a1.44 1.44 0 0 1 .25-.53c.17-.228.363-.435.593-.656.45-.436 1.01-.737 1.46-.94-.042.207-.104.444-.052.69.05.23.25.38.44.47.26.12.506.152.69.153 1.42.01 2.86 0 4.28 0 .246 0 .45-.163.593-.375.14-.21.25-.48.343-.845.13-.5.094-1.062-.094-1.625a4.812 4.812 0 0 0-.72-1.406c-.336-.444-.675-.83-1-1.22 1.256-.815 2.715-1.24 3.97-1.688.12-.452.222-.926.31-1.313zm-9.47 8.438c-.26.394-.583.69-.874 1 .38.286.75.556 1.1.813.336-.303.627-.674.876-.97-.39-.267-.77-.587-1.093-.843z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="italic-arab-meem">
<path id="arab-meem" d="M16 9.73l-.93 2.19h-4.663c-.48 0-.857.12-1.135.366l-.06.11c-.185 2.016-.503 3.558-.956 4.627a8.31 8.31 0 0 1-1.082 1.833c-.177.226-.22.186-.126-.12l.142-.503.17-.67.234-.87.002-.008.202-1.045.258-1.41.353-1.907c.19-.312.42-.638.692-.98a24.1 24.1 0 0 1 .94-1.09c.13-.092.697-.18 1.705-.266 1.05-.086 1.64-.183 1.765-.293l.065-.128c.01-.11-.01-.24-.052-.394a2.403 2.403 0 0 0-.232-.522c-.22-.428-.438-.64-.654-.64-.294 0-.915.268-1.864.805-.36.208-.378.125-.05-.247 1.555-1.71 2.705-2.566 3.45-2.566.38 0 .67.13.86.394.134.195.25.6.343 1.21l.202 1.2c.105.586.24.895.408.925"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="italic-arab-meem">
+ <path id="arab-meem" d="M16 9.73l-.93 2.19h-4.663c-.48 0-.857.12-1.135.366l-.06.11c-.185 2.016-.503 3.558-.956 4.627a8.31 8.31 0 0 1-1.082 1.833c-.177.226-.22.186-.126-.12l.142-.503.17-.67.234-.87.002-.008.202-1.045.258-1.41.353-1.907c.19-.312.42-.638.692-.98a24.1 24.1 0 0 1 .94-1.09c.13-.092.697-.18 1.705-.266 1.05-.086 1.64-.183 1.765-.293l.065-.128c.01-.11-.01-.24-.052-.394a2.403 2.403 0 0 0-.232-.522c-.22-.428-.438-.64-.654-.64-.294 0-.915.268-1.864.805-.36.208-.378.125-.05-.247 1.555-1.71 2.705-2.566 3.45-2.566.38 0 .67.13.86.394.134.195.25.6.343 1.21l.202 1.2c.105.586.24.895.408.925"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="italic-armn-sha">
<path id="armn-sha" d="M11.564 7.678a3.073 3.073 0 0 0-.93-.268c-.35-.047-.75-.07-1.197-.07h-1.11L8.587 6h1.723c.558 0 1.042.032 1.45.095.416.063.794.173 1.136.33l4.483 2.033-.33 1.67-2.625-1.165a1.867 1.867 0 0 0-.433-.134 2.45 2.45 0 0 0-.576-.06 4.88 4.88 0 0 0-1.663.28c-.526.19-1 .46-1.427.812-.42.35-.776.78-1.07 1.283a5.48 5.48 0 0 0-.63 1.71c-.24 1.255-.15 2.21.27 2.87.424.65 1.19.976 2.292.976.55 0 1.044-.08 1.48-.236a3.488 3.488 0 0 0 1.135-.66c.325-.29.59-.634.795-1.034.21-.4.363-.84.458-1.322l.11-.56h1.6l-.12.59a5.925 5.925 0 0 1-.676 1.844 5.19 5.19 0 0 1-1.214 1.423c-.488.395-1.053.7-1.694.923a6.573 6.573 0 0 1-2.106.324c-.767 0-1.434-.114-2-.34-.568-.226-1.025-.554-1.372-.985-.347-.437-.573-.97-.678-1.608-.105-.64-.078-1.366.08-2.186.125-.66.346-1.274.66-1.836A6.332 6.332 0 0 1 8.792 9.54a5.955 5.955 0 0 1 1.496-1.072 5.87 5.87 0 0 1 1.732-.57l-.465-.23"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="italic-armn-sha">
+ <path id="armn-sha" d="M11.564 7.678a3.073 3.073 0 0 0-.93-.268c-.35-.047-.75-.07-1.197-.07h-1.11L8.587 6h1.723c.558 0 1.042.032 1.45.095.416.063.794.173 1.136.33l4.483 2.033-.33 1.67-2.625-1.165a1.867 1.867 0 0 0-.433-.134 2.45 2.45 0 0 0-.576-.06 4.88 4.88 0 0 0-1.663.28c-.526.19-1 .46-1.427.812-.42.35-.776.78-1.07 1.283a5.48 5.48 0 0 0-.63 1.71c-.24 1.255-.15 2.21.27 2.87.424.65 1.19.976 2.292.976.55 0 1.044-.08 1.48-.236a3.488 3.488 0 0 0 1.135-.66c.325-.29.59-.634.795-1.034.21-.4.363-.84.458-1.322l.11-.56h1.6l-.12.59a5.925 5.925 0 0 1-.676 1.844 5.19 5.19 0 0 1-1.214 1.423c-.488.395-1.053.7-1.694.923a6.573 6.573 0 0 1-2.106.324c-.767 0-1.434-.114-2-.34-.568-.226-1.025-.554-1.372-.985-.347-.437-.573-.97-.678-1.608-.105-.64-.078-1.366.08-2.186.125-.66.346-1.274.66-1.836A6.332 6.332 0 0 1 8.792 9.54a5.955 5.955 0 0 1 1.496-1.072 5.87 5.87 0 0 1 1.732-.57l-.465-.23"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="italic-c">
<path id="c" d="M15.008 13.718l1.48.214c-.467 1.34-1.15 2.354-2.045 3.04a4.835 4.835 0 0 1-3.015 1.03c-1.36 0-2.438-.43-3.237-1.29C7.4 15.85 7 14.618 7 13.012c0-2.09.606-3.817 1.817-5.184C9.897 6.61 11.237 6 12.84 6c1.186 0 2.145.33 2.878.99.738.66 1.165 1.546 1.282 2.66l-1.397.135c-.148-.84-.453-1.464-.916-1.876-.458-.42-1.05-.63-1.78-.63-1.368 0-2.475.63-3.32 1.89-.733 1.087-1.1 2.377-1.1 3.87 0 1.194.283 2.104.848 2.732.565.628 1.3.942 2.206.942.78 0 1.48-.26 2.1-.785.63-.52 1.08-1.26 1.37-2.216"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="italic-c">
+ <path id="c" d="M15.008 13.718l1.48.214c-.467 1.34-1.15 2.354-2.045 3.04a4.835 4.835 0 0 1-3.015 1.03c-1.36 0-2.438-.43-3.237-1.29C7.4 15.85 7 14.618 7 13.012c0-2.09.606-3.817 1.817-5.184C9.897 6.61 11.237 6 12.84 6c1.186 0 2.145.33 2.878.99.738.66 1.165 1.546 1.282 2.66l-1.397.135c-.148-.84-.453-1.464-.916-1.876-.458-.42-1.05-.63-1.78-.63-1.368 0-2.475.63-3.32 1.89-.733 1.087-1.1 2.377-1.1 3.87 0 1.194.283 2.104.848 2.732.565.628 1.3.942 2.206.942.78 0 1.48-.26 2.1-.785.63-.52 1.08-1.26 1.37-2.216"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="italic-d">
<path id="d" d="M7 18L9.462 6h3.557c.85 0 1.5.063 1.95.188.642.17 1.192.472 1.65.91.454.43.8.97 1.03 1.62.23.65.344 1.378.344 2.186 0 .966-.146 1.847-.436 2.644-.284.79-.66 1.49-1.127 2.095-.46.6-.946 1.072-1.455 1.416-.504.33-1.1.582-1.794.75-.525.122-1.17.19-1.94.19H7m1.86-1.36h1.866c.842 0 1.59-.08 2.245-.24a3.26 3.26 0 0 0 1.05-.436 4.19 4.19 0 0 0 1.04-.975 6.652 6.652 0 0 0 .975-1.825c.247-.687.37-1.467.37-2.34 0-.97-.166-1.716-.5-2.235-.332-.522-.755-.87-1.27-1.04-.38-.124-.974-.186-1.78-.186H11L9.095 16.64"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="italic-d">
+ <path id="d" d="M7 18L9.462 6h3.557c.85 0 1.5.063 1.95.188.642.17 1.192.472 1.65.91.454.43.8.97 1.03 1.62.23.65.344 1.378.344 2.186 0 .966-.146 1.847-.436 2.644-.284.79-.66 1.49-1.127 2.095-.46.6-.946 1.072-1.455 1.416-.504.33-1.1.582-1.794.75-.525.122-1.17.19-1.94.19H7m1.86-1.36h1.866c.842 0 1.59-.08 2.245-.24a3.26 3.26 0 0 0 1.05-.436 4.19 4.19 0 0 0 1.04-.975 6.652 6.652 0 0 0 .975-1.825c.247-.687.37-1.467.37-2.34 0-.97-.166-1.716-.5-2.235-.332-.522-.755-.87-1.27-1.04-.38-.124-.974-.186-1.78-.186H11L9.095 16.64"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="italic-e">
<path id="e" d="M7 18L9.474 6H18l-.282 1.367H10.77L10.02 11h6.09l-.28 1.367H9.74l-.88 4.273h7.44L16.018 18H7"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="italic-e">
+ <path id="e" d="M7 18L9.474 6H18l-.282 1.367H10.77L10.02 11h6.09l-.28 1.367H9.74l-.88 4.273h7.44L16.018 18H7"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="italic-geor-kan">
<path id="geor-kan" d="M15.057 14.663C14.617 16.888 13.223 18 10.88 18 8.96 18 8 17.213 8 15.64c0-.298.036-.624.108-.977.083-.43.245-.836.488-1.217l1.24.605-.206.62c-.055.26-.083.497-.083.71 0 .97.52 1.46 1.564 1.46 1.31 0 2.108-.724 2.39-2.17l.058-.33a3.17 3.17 0 0 0 .066-.615c0-.927-.546-1.39-1.64-1.39H10.87l.247-1.26h1.118c1.203-.004 1.91-.55 2.12-1.64.04-.18.057-.355.057-.52 0-1.144-.9-1.715-2.696-1.715L11.94 6C14.646 6 16 6.877 16 8.627c0 .248-.027.516-.082.803-.204 1.092-1.05 1.824-2.54 2.194l-.033.166c1.23.2 1.845.823 1.845 1.872 0 .21-.025.433-.074.67l-.058.332"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="italic-geor-kan">
+ <path id="geor-kan" d="M15.057 14.663C14.617 16.888 13.223 18 10.88 18 8.96 18 8 17.213 8 15.64c0-.298.036-.624.108-.977.083-.43.245-.836.488-1.217l1.24.605-.206.62c-.055.26-.083.497-.083.71 0 .97.52 1.46 1.564 1.46 1.31 0 2.108-.724 2.39-2.17l.058-.33a3.17 3.17 0 0 0 .066-.615c0-.927-.546-1.39-1.64-1.39H10.87l.247-1.26h1.118c1.203-.004 1.91-.55 2.12-1.64.04-.18.057-.355.057-.52 0-1.144-.9-1.715-2.696-1.715L11.94 6C14.646 6 16 6.877 16 8.627c0 .248-.027.516-.082.803-.204 1.092-1.05 1.824-2.54 2.194l-.033.166c1.23.2 1.845.823 1.845 1.872 0 .21-.025.433-.074.67l-.058.332"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="italic-i">
<path id="i" d="M12.5 18l.25-.995h-1.5l2.508-10.037h1.5L15.5 6h-5l-.242.968h1.5l-2.51 10.037h-1.5L7.5 18z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="italic-i">
+ <path id="i" d="M12.5 18l.25-.995h-1.5l2.508-10.037h1.5L15.5 6h-5l-.242.968h1.5l-2.51 10.037h-1.5L7.5 18z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="italic-k">
<path id="k" d="M12.018 10.652L17 6h-2l-5.31 5.234L11 6H9.5l-3 12H8l1.173-4.693 1.54-1.438C11 16 14 18 14 18h2s-4-2-3.982-7.348z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="italic-k">
+ <path id="k" d="M12.018 10.652L17 6h-2l-5.31 5.234L11 6H9.5l-3 12H8l1.173-4.693 1.54-1.438C11 16 14 18 14 18h2s-4-2-3.982-7.348z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="italic-s">
<path id="s" d="M16.474 6.59l-.302 1.525a7.36 7.36 0 0 0-1.557-.628 5.432 5.432 0 0 0-1.487-.217c-.935 0-1.68.204-2.23.612-.554.408-.83.95-.83 1.627 0 .37.1.65.302.86.207.19.733.4 1.58.63l.937.23c1.06.274 1.795.622 2.208 1.046.413.42.62 1.007.62 1.766 0 1.167-.46 2.117-1.38 2.85-.913.734-2.12 1.1-3.617 1.1-.615 0-1.232-.06-1.852-.185-.62-.12-1.242-.3-1.867-.55l.31-1.61a7.613 7.613 0 0 0 1.72.805c.58.18 1.155.27 1.73.27.976 0 1.76-.216 2.347-.65.59-.434.883-1 .883-1.697 0-.465-.12-.816-.354-1.054-.233-.242-.737-.46-1.512-.657l-.937-.24c-1.07-.28-1.8-.6-2.19-.964-.39-.368-.584-.88-.584-1.535 0-1.152.442-2.094 1.325-2.828.89-.74 2.043-1.108 3.463-1.108.555 0 1.1.05 1.644.146.542.1 1.085.245 1.627.442"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="italic-s">
+ <path id="s" d="M16.474 6.59l-.302 1.525a7.36 7.36 0 0 0-1.557-.628 5.432 5.432 0 0 0-1.487-.217c-.935 0-1.68.204-2.23.612-.554.408-.83.95-.83 1.627 0 .37.1.65.302.86.207.19.733.4 1.58.63l.937.23c1.06.274 1.795.622 2.208 1.046.413.42.62 1.007.62 1.766 0 1.167-.46 2.117-1.38 2.85-.913.734-2.12 1.1-3.617 1.1-.615 0-1.232-.06-1.852-.185-.62-.12-1.242-.3-1.867-.55l.31-1.61a7.613 7.613 0 0 0 1.72.805c.58.18 1.155.27 1.73.27.976 0 1.76-.216 2.347-.65.59-.434.883-1 .883-1.697 0-.465-.12-.816-.354-1.054-.233-.242-.737-.46-1.512-.657l-.937-.24c-1.07-.28-1.8-.6-2.19-.964-.39-.368-.584-.88-.584-1.535 0-1.152.442-2.094 1.325-2.828.89-.74 2.043-1.108 3.463-1.108.555 0 1.1.05 1.644.146.542.1 1.085.245 1.627.442"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M16 9V8h-6v1h6zm-2 2v-1h-4v1h4zM6 5h1v16H6V5zm2 0h10v13c0 1.7-1.3 3-3 3H8V5z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M16 9V8h-6v1h6zm-2 2v-1h-4v1h4zM6 5h1v16H6V5zm2 0h10v13c0 1.7-1.3 3-3 3H8V5z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M8 9V8h6v1H8zm2 2v-1h4v1h-4zm8-6h-1v16h1V5zm-2 0H6v13c0 1.7 1.3 3 3 3h7V5z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M8 9V8h6v1H8zm2 2v-1h4v1h-4zm8-6h-1v16h1V5zm-2 0H6v13c0 1.7 1.3 3 3 3h7V5z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M14.5 4C11.5 4 9 6.5 9 9.5c0 1 .3 1.9.7 2.8L4 18v2h4v-2h2v-2h2l1.2-1.2c.4.1.9.2 1.3.2 3 0 5.5-2.5 5.5-5.5S17.5 4 14.5 4zM16 9c-.8 0-1.5-.7-1.5-1.5S15.2 6 16 6s1.5.7 1.5 1.5S16.8 9 16 9z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M14.5 4C11.5 4 9 6.5 9 9.5c0 1 .3 1.9.7 2.8L4 18v2h4v-2h2v-2h2l1.2-1.2c.4.1.9.2 1.3.2 3 0 5.5-2.5 5.5-5.5S17.5 4 14.5 4zM16 9c-.8 0-1.5-.7-1.5-1.5S15.2 6 16 6s1.5.7 1.5 1.5S16.8 9 16 9z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M9.5 4c3 0 5.5 2.5 5.5 5.5 0 1-.3 1.9-.7 2.8L20 18v2h-4v-2h-2v-2h-2l-1.2-1.2c-.4.1-.9.2-1.3.2-3 0-5.5-2.5-5.5-5.5S6.5 4 9.5 4zM8 9c.8 0 1.5-.7 1.5-1.5S8.8 6 8 6s-1.5.7-1.5 1.5S7.2 9 8 9z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M9.5 4c3 0 5.5 2.5 5.5 5.5 0 1-.3 1.9-.7 2.8L20 18v2h-4v-2h-2v-2h-2l-1.2-1.2c-.4.1-.9.2-1.3.2-3 0-5.5-2.5-5.5-5.5S6.5 4 9.5 4zM8 9c.8 0 1.5-.7 1.5-1.5S8.8 6 8 6s-1.5.7-1.5 1.5S7.2 9 8 9z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M3 7v9c0 1.7 1.3 3 3 3h15V7H3zm8 2h2v2h-2V9zm0 3h2v2h-2v-2zM8 9h2v2H8V9zm0 3h2v2H8v-2zm-1 5H6c-.6 0-1-.4-1-1v-1h2v2zm0-3H5v-2h2v2zm0-3H5V9h2v2zm9 6H8v-2h8v2zm0-3h-2v-2h2v2zm0-3h-2V9h2v2zm3 6h-2v-2h2v2zm0-3h-2v-2h2v2zm0-3h-2V9h2v2z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M3 7v9c0 1.7 1.3 3 3 3h15V7H3zm8 2h2v2h-2V9zm0 3h2v2h-2v-2zM8 9h2v2H8V9zm0 3h2v2H8v-2zm-1 5H6c-.6 0-1-.4-1-1v-1h2v2zm0-3H5v-2h2v2zm0-3H5V9h2v2zm9 6H8v-2h8v2zm0-3h-2v-2h2v2zm0-3h-2V9h2v2zm3 6h-2v-2h2v2zm0-3h-2v-2h2v2zm0-3h-2V9h2v2z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M21 7v9c0 1.7-1.3 3-3 3H3V7h18zm-8 2h-2v2h2V9zm0 3h-2v2h2v-2zm3-3h-2v2h2V9zm0 3h-2v2h2v-2zm1 5h1c.6 0 1-.4 1-1v-1h-2v2zm0-3h2v-2h-2v2zm0-3h2V9h-2v2zm-9 6h8v-2H8v2zm0-3h2v-2H8v2zm0-3h2V9H8v2zm-3 6h2v-2H5v2zm0-3h2v-2H5v2zm0-3h2V9H5v2z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M21 7v9c0 1.7-1.3 3-3 3H3V7h18zm-8 2h-2v2h2V9zm0 3h-2v2h2v-2zm3-3h-2v2h2V9zm0 3h-2v2h2v-2zm1 5h1c.6 0 1-.4 1-1v-1h-2v2zm0-3h2v-2h-2v2zm0-3h2V9h-2v2zm-9 6h8v-2H8v2zm0-3h2v-2H8v2zm0-3h2V9H8v2zm-3 6h2v-2H5v2zm0-3h2v-2H5v2zm0-3h2V9H5v2z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="A">
<path d="M18.738 15.673l1.137 3.15h1.575L17.775 7.448h-2.188l-3.85 11.375h1.575l1.05-3.15h4.375zM16.55 8.76l1.837 5.427h-3.675l1.838-5.425z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="A">
+ <path d="M18.738 15.673l1.137 3.15h1.575L17.775 7.448h-2.188l-3.85 11.375h1.575l1.05-3.15h4.375zM16.55 8.76l1.837 5.427h-3.675l1.838-5.425z"/>
+ </g>
+ <g id="文">
+ <path d="M8.325 6.573h.787l-.875-1.75h-1.75l.438.875a1.56 1.56 0 0 0 1.4.875z"/>
+ <path d="m 9.202,12.874 c 0.7,0.525 1.486,0.963 2.45,1.225 l -0.438,1.31 A 9.17,9.17 0 0 1 8.151,13.835 c -1.49,1.137 -3.063,1.837 -4.813,2.363 L 2.9,14.885 C 4.386,14.36 5.874,13.835 7.1,12.872 5.962,11.648 5.174,10.335 4.65,8.758 l -1.663,0 0,-1.31 10.85,0 -0.438,1.312 -1.75,0 c -0.308,1.33 -1.255,2.957 -2.45,4.114 z m 1.05,-4.114 -4.114,0 c 0.35,1.226 1.138,2.363 2.013,3.238 0.926,-1 1.617,-1.957 2.1,-3.237 z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="A">
<path d="M5.612 15.673l-1.137 3.15H2.9L6.575 7.448h2.188l3.85 11.375h-1.575l-1.05-3.15H5.613zM7.8 8.76l-1.837 5.427h3.675L7.8 8.762z" id="path5"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="A">
+ <path d="M5.612 15.673l-1.137 3.15H2.9L6.575 7.448h2.188l3.85 11.375h-1.575l-1.05-3.15H5.613zM7.8 8.76l-1.837 5.427h3.675L7.8 8.762z" id="path5"/>
+ </g>
+ <g id="文">
+ <path d="M16.384 6.573h.787l-.873-1.75h-1.75l.438.875c.26.535.805.874 1.4.875z" id="path7"/>
+ <path d="M15.15 12.874c-.7.525-1.486.963-2.45 1.225l.438 1.31a9.17 9.17 0 0 0 3.063-1.575c1.49 1.137 3.064 1.837 4.814 2.363l.438-1.313c-1.486-.525-2.974-1.05-4.2-2.013 1.138-1.224 1.926-2.537 2.45-4.114h1.663v-1.31h-10.85l.438 1.312h1.75c.308 1.33 1.255 2.957 2.45 4.114zM14.1 8.76h4.114c-.35 1.226-1.138 2.363-2.013 3.238-.925-1-1.616-1.957-2.1-3.237z" id="path11-7"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="layout-ltr">
<path id="text" d="M5 19V5h6v8h8v6H5z"/>
<path id="float" d="M13 5v6h6V5h-6zm5 5h-4V6h4v4z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="layout-ltr">
+ <path id="text" d="M5 19V5h6v8h8v6H5z"/>
+ <path id="float" d="M13 5v6h6V5h-6zm5 5h-4V6h4v4z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="layout-rtl">
<path id="text" d="M5 19v-6h8V5h6v14H5z"/>
<path id="float" d="M5 5v6h6V5H5zm1 1h4v4H6V6z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="layout-rtl">
+ <path id="text" d="M5 19v-6h8V5h6v14H5z"/>
+ <path id="float" d="M5 5v6h6V5H5zm1 1h4v4H6V6z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M15.387 4.33c-2.1 0-3.6 1.9-5.1 3.3.2 0 .5-.1.8-.1.5 0 1 .1 1.5.3.8-.8 1.6-1.7 2.8-1.7.6 0 1.3.3 1.8.7 1 1 1 2.6 0 3.6l-2.6 2.6c-.4.4-1.2.7-1.8.7-1.4 0-2.1-.9-2.6-2l-1.3 1.3c.8 1.5 2 2.6 3.8 2.6 1.2 0 2.3-.5 3-1.3l2.6-2.6c.9-.9 1.5-2 1.5-3.3-.2-2.2-2.2-4.1-4.4-4.1zm-4.3 12.1l-.9.9c-.4.4-1.2.7-1.8.7-.6 0-1.3-.3-1.8-.7-1-1-1-2.7 0-3.6l2.6-2.6c.4-.4 1.2-.7 1.8-.7 1.4 0 2.1 1 2.6 2l1.3-1.3c-.8-1.5-2-2.6-3.8-2.6-1.2 0-2.3.5-3 1.3l-2.6 2.6c-1.7 1.7-1.7 4.4 0 6 1.6 1.6 4.4 1.7 5.9 0l1.9-1.9c-.3.1-.6.1-.9.1-.5 0-.9 0-1.3-.2z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M15.387 4.33c-2.1 0-3.6 1.9-5.1 3.3.2 0 .5-.1.8-.1.5 0 1 .1 1.5.3.8-.8 1.6-1.7 2.8-1.7.6 0 1.3.3 1.8.7 1 1 1 2.6 0 3.6l-2.6 2.6c-.4.4-1.2.7-1.8.7-1.4 0-2.1-.9-2.6-2l-1.3 1.3c.8 1.5 2 2.6 3.8 2.6 1.2 0 2.3-.5 3-1.3l2.6-2.6c.9-.9 1.5-2 1.5-3.3-.2-2.2-2.2-4.1-4.4-4.1zm-4.3 12.1l-.9.9c-.4.4-1.2.7-1.8.7-.6 0-1.3-.3-1.8-.7-1-1-1-2.7 0-3.6l2.6-2.6c.4-.4 1.2-.7 1.8-.7 1.4 0 2.1 1 2.6 2l1.3-1.3c-.8-1.5-2-2.6-3.8-2.6-1.2 0-2.3.5-3 1.3l-2.6 2.6c-1.7 1.7-1.7 4.4 0 6 1.6 1.6 4.4 1.7 5.9 0l1.9-1.9c-.3.1-.6.1-.9.1-.5 0-.9 0-1.3-.2z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M9.025 3.6c2.1 0 3.6 1.9 5.1 3.3-.2 0-.5-.1-.8-.1-.5 0-1 .1-1.5.3-.8-.8-1.6-1.7-2.8-1.7-.6 0-1.3.3-1.8.7-1 1-1 2.6 0 3.6l2.6 2.6c.4.4 1.2.7 1.8.7 1.4 0 2.1-.9 2.6-2l1.3 1.3c-.8 1.5-2 2.6-3.8 2.6-1.2 0-2.3-.5-3-1.3l-2.6-2.6c-.9-.9-1.5-2-1.5-3.3.2-2.2 2.2-4.1 4.4-4.1zm4.3 12.1l.9.9c.4.4 1.2.7 1.8.7.6 0 1.3-.3 1.8-.7 1-1 1-2.7 0-3.6l-2.6-2.6c-.4-.4-1.2-.7-1.8-.7-1.4 0-2.1 1-2.6 2l-1.3-1.3c.8-1.5 2-2.6 3.8-2.6 1.2 0 2.3.5 3 1.3l2.6 2.6c1.7 1.7 1.7 4.4 0 6-1.6 1.6-4.4 1.7-5.9 0l-1.9-1.9c.3.1.6.1.9.1.5 0 .9 0 1.3-.2z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M9.025 3.6c2.1 0 3.6 1.9 5.1 3.3-.2 0-.5-.1-.8-.1-.5 0-1 .1-1.5.3-.8-.8-1.6-1.7-2.8-1.7-.6 0-1.3.3-1.8.7-1 1-1 2.6 0 3.6l2.6 2.6c.4.4 1.2.7 1.8.7 1.4 0 2.1-.9 2.6-2l1.3 1.3c-.8 1.5-2 2.6-3.8 2.6-1.2 0-2.3-.5-3-1.3l-2.6-2.6c-.9-.9-1.5-2-1.5-3.3.2-2.2 2.2-4.1 4.4-4.1zm4.3 12.1l.9.9c.4.4 1.2.7 1.8.7.6 0 1.3-.3 1.8-.7 1-1 1-2.7 0-3.6l-2.6-2.6c-.4-.4-1.2-.7-1.8-.7-1.4 0-2.1 1-2.6 2l-1.3-1.3c.8-1.5 2-2.6 3.8-2.6 1.2 0 2.3.5 3 1.3l2.6 2.6c1.7 1.7 1.7 4.4 0 6-1.6 1.6-4.4 1.7-5.9 0l-1.9-1.9c.3.1.6.1.9.1.5 0 .9 0 1.3-.2z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M21 7H9V5h12v2zM7 6c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm14 7H9v-2h12v2zM7 12c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm14 7H9v-2h12v2zM7 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M21 7H9V5h12v2zM7 6c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm14 7H9v-2h12v2zM7 12c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm14 7H9v-2h12v2zM7 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M3 7h12V5H3v2zm14-1c0 1.1.9 2 2 2s2-.9 2-2-.9-2-2-2-2 .9-2 2zM3 13h12v-2H3v2zm14-1c0 1.1.9 2 2 2s2-.9 2-2-.9-2-2-2-2 .9-2 2zM3 19h12v-2H3v2zm14-1c0 1.1.9 2 2 2s2-.9 2-2-.9-2-2-2-2 .9-2 2z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M3 7h12V5H3v2zm14-1c0 1.1.9 2 2 2s2-.9 2-2-.9-2-2-2-2 .9-2 2zM3 13h12v-2H3v2zm14-1c0 1.1.9 2 2 2s2-.9 2-2-.9-2-2-2-2 .9-2 2zM3 19h12v-2H3v2zm14-1c0 1.1.9 2 2 2s2-.9 2-2-.9-2-2-2-2 .9-2 2z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M21 7H8V5h13v2zm0 6H8v-2h13v2zm0 6H8v-2h13v2zM4 4h2v4H5V5H4zm-1 6V9h3v3H4v1h2v1H3v-3h2v-1zm3 10H3v-1h2v-1H4v-1h1v-1H3v-1h3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M21 7H8V5h13v2zm0 6H8v-2h13v2zm0 6H8v-2h13v2zM4 4h2v4H5V5H4zm-1 6V9h3v3H4v1h2v1H3v-3h2v-1zm3 10H3v-1h2v-1H4v-1h1v-1H3v-1h3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M3 7h13V5H3zm0 6h13v-2H3zm0 6h13v-2H3zM18 4h2v4h-1V5h-1zm0 6V9h3v3h-2v1h2v1h-3v-3h2v-1zm3 10h-3v-1h2v-1h-1v-1h1v-1h-2v-1h3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M3 7h13V5H3zm0 6h13v-2H3zm0 6h13v-2H3zM18 4h2v4h-1V5h-1zm0 6V9h3v3h-2v1h2v1h-3v-3h2v-1zm3 10h-3v-1h2v-1h-1v-1h1v-1h-2v-1h3z"/>
+</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: #d11d13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</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>
--- /dev/null
+<?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: #36c }</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: #d11d13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</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>
--- /dev/null
+<?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: #36c }</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M15 14v3l5-4.5L15 8v3H8c0 1.7 1.3 3 3 3h4zm-1-9H4v15h10v-2H6V7h8V5z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M15 14v3l5-4.5L15 8v3H8c0 1.7 1.3 3 3 3h4zm-1-9H4v15h10v-2H6V7h8V5z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M9 14v3l-5-4.5L9 8v3h7c0 1.7-1.3 3-3 3H9zm1-9h10v15H10v-2h8V7h-8V5z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M9 14v3l-5-4.5L9 8v3h7c0 1.7-1.3 3-3 3H9zm1-9h10v15H10v-2h8V7h-8V5z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M12 6c-3.9 0-7 3.1-7 7s3.1 7 7 7 7-3.1 7-7-3.1-7-7-7zm0 13c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm-1.7-4.6c-.7 0-1-.4-1-1.2s.3-1.2 1-1.2c.4 0 .6.2.8.6l.9-.5c-.4-.7-1-1-1.9-1-.6 0-1.1.2-1.5.6s-.6.8-.6 1.5.2 1.2.6 1.6c.4.4.9.6 1.5.6.8 0 1.4-.4 1.9-1.1l-.9-.4c-.2.3-.5.5-.8.5zm4 0c-.7 0-1-.4-1-1.2s.3-1.2 1-1.2c.4 0 .6.2.8.6l.9-.5c-.4-.7-1-1-1.9-1-.6 0-1.1.2-1.5.6s-.6.8-.6 1.5.2 1.2.6 1.6c.4.4.9.6 1.5.6.8 0 1.4-.4 1.9-1.1l-.9-.4c-.2.3-.5.5-.8.5z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M12 6c-3.9 0-7 3.1-7 7s3.1 7 7 7 7-3.1 7-7-3.1-7-7-7zm0 13c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm-1.7-4.6c-.7 0-1-.4-1-1.2s.3-1.2 1-1.2c.4 0 .6.2.8.6l.9-.5c-.4-.7-1-1-1.9-1-.6 0-1.1.2-1.5.6s-.6.8-.6 1.5.2 1.2.6 1.6c.4.4.9.6 1.5.6.8 0 1.4-.4 1.9-1.1l-.9-.4c-.2.3-.5.5-.8.5zm4 0c-.7 0-1-.4-1-1.2s.3-1.2 1-1.2c.4 0 .6.2.8.6l.9-.5c-.4-.7-1-1-1.9-1-.6 0-1.1.2-1.5.6s-.6.8-.6 1.5.2 1.2.6 1.6c.4.4.9.6 1.5.6.8 0 1.4-.4 1.9-1.1l-.9-.4c-.2.3-.5.5-.8.5z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M15.4 7.8c-2-.9-2.3-2.5-2.4-2.8.1.1 2 1 2 1l-3-5-3 5 2-1s0 .8.6 2.1c.8 1.5 2.2 2.2 2.2 2.2s1.6.7 2.2 1.3l-.7.7-.5-.5-.4 1.8 1.8-.4-.5-.5.7-.7c.9 1 1.5 2.3 1.6 3.8h-1V14l-1.5 1 1.5 1v-.8h1c-.1 1.5-.6 2.8-1.6 3.8l-.7-.7.5-.5-1.8-.4.4 1.8.5-.5.7.7c-1 .9-2.3 1.5-3.8 1.6v-1h.8l-1-1.5-1 1.5h.8v1c-1.5-.1-2.8-.6-3.8-1.6l.7-.7.5.5.4-1.8-1.8.4.5.5-.7.7c-.9-1-1.5-2.3-1.6-3.8h1v.8l1.5-1L7 14v.8H6c.1-1.5.6-2.8 1.6-3.8l.7.7-.5.5 1.8.4-.4-1.8-.5.5-.7-.7-1.5-1.4A7.99 7.99 0 0 0 4 15c0 4.4 3.6 8 8 8s8-3.6 8-8c0-3.2-1.9-5.9-4.6-7.2z"/>
<circle cx="12" cy="15" r="3"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M15.4 7.8c-2-.9-2.3-2.5-2.4-2.8.1.1 2 1 2 1l-3-5-3 5 2-1s0 .8.6 2.1c.8 1.5 2.2 2.2 2.2 2.2s1.6.7 2.2 1.3l-.7.7-.5-.5-.4 1.8 1.8-.4-.5-.5.7-.7c.9 1 1.5 2.3 1.6 3.8h-1V14l-1.5 1 1.5 1v-.8h1c-.1 1.5-.6 2.8-1.6 3.8l-.7-.7.5-.5-1.8-.4.4 1.8.5-.5.7.7c-1 .9-2.3 1.5-3.8 1.6v-1h.8l-1-1.5-1 1.5h.8v1c-1.5-.1-2.8-.6-3.8-1.6l.7-.7.5.5.4-1.8-1.8.4.5.5-.7.7c-.9-1-1.5-2.3-1.6-3.8h1v.8l1.5-1L7 14v.8H6c.1-1.5.6-2.8 1.6-3.8l.7.7-.5.5 1.8.4-.4-1.8-.5.5-.7-.7-1.5-1.4A7.99 7.99 0 0 0 4 15c0 4.4 3.6 8 8 8s8-3.6 8-8c0-3.2-1.9-5.9-4.6-7.2z"/>
+ <circle cx="12" cy="15" r="3"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M22.3 6.3c0 .2 0 .3-.1.3-.7.1-1.2.5-1.6 1.1-.1.2-.2.4-.3.7l-4.6 10.1c-.1.2-.2.3-.2.3s-.1.1-.2.1c-.2 0-.4-.1-.5-.4L12.2 13l-2.8 5.5c-.1.3-.3.4-.5.4s-.4-.1-.5-.4L4.1 8.4c-.3-.8-.6-1.2-.8-1.4-.2-.2-.5-.3-1-.4-.1-.1-.1-.2-.1-.3 0-.2 0-.3.1-.3h4.3c.1.1.1.2.1.3 0 .2 0 .3-.1.3-.6.1-1 .2-1.1.4-.1.2 0 .6.3 1.2l3.6 8.2h.1l2.2-4.4L10 8.4c-.3-.7-.6-1.2-.8-1.4s-.5-.3-.9-.4c-.1-.1-.1-.2-.1-.3 0-.2 0-.3.1-.3h3.6c.1.1.1.2.1.3 0 .2 0 .3-.1.3-.4.1-.6.2-.6.4s.1.6.4 1.2l1 1.9 1-1.9c.3-.6.5-.9.5-1.1 0-.2 0-.3-.1-.4-.1-.1-.3-.1-.5-.1l-.1-.3c0-.2 0-.3.1-.3h3c.1.1.1.2.1.3 0 .2 0 .3-.1.3-.5.1-.8.2-1.1.5-.3.3-.6.7-.8 1.3l-1.3 2.8 2.5 5.2h.1l3.7-8.1c.3-.5.3-.9.2-1.2-.1-.3-.5-.4-1.1-.5-.1-.1-.1-.2-.1-.3s0-.3.1-.3h3.7c-.2.1-.2.2-.2.3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M22.3 6.3c0 .2 0 .3-.1.3-.7.1-1.2.5-1.6 1.1-.1.2-.2.4-.3.7l-4.6 10.1c-.1.2-.2.3-.2.3s-.1.1-.2.1c-.2 0-.4-.1-.5-.4L12.2 13l-2.8 5.5c-.1.3-.3.4-.5.4s-.4-.1-.5-.4L4.1 8.4c-.3-.8-.6-1.2-.8-1.4-.2-.2-.5-.3-1-.4-.1-.1-.1-.2-.1-.3 0-.2 0-.3.1-.3h4.3c.1.1.1.2.1.3 0 .2 0 .3-.1.3-.6.1-1 .2-1.1.4-.1.2 0 .6.3 1.2l3.6 8.2h.1l2.2-4.4L10 8.4c-.3-.7-.6-1.2-.8-1.4s-.5-.3-.9-.4c-.1-.1-.1-.2-.1-.3 0-.2 0-.3.1-.3h3.6c.1.1.1.2.1.3 0 .2 0 .3-.1.3-.4.1-.6.2-.6.4s.1.6.4 1.2l1 1.9 1-1.9c.3-.6.5-.9.5-1.1 0-.2 0-.3-.1-.4-.1-.1-.3-.1-.5-.1l-.1-.3c0-.2 0-.3.1-.3h3c.1.1.1.2.1.3 0 .2 0 .3-.1.3-.5.1-.8.2-1.1.5-.3.3-.6.7-.8 1.3l-1.3 2.8 2.5 5.2h.1l3.7-8.1c.3-.5.3-.9.2-1.2-.1-.3-.5-.4-1.1-.5-.1-.1-.1-.2-.1-.3s0-.3.1-.3h3.7c-.2.1-.2.2-.2.3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M15 6L9 4 3 6v15l6-2 6 2 6-2V4l-6 2zM8.7 18.1L4 19.6V6.7L9 5v12.9l-.3.2zm11.3.2L15 20V7.1l.3-.1L20 5.4v12.9z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M15 6L9 4 3 6v15l6-2 6 2 6-2V4l-6 2zM8.7 18.1L4 19.6V6.7L9 5v12.9l-.3.2zm11.3.2L15 20V7.1l.3-.1L20 5.4v12.9z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M9 6l6-2 6 2v15l-6-2-6 2-6-2V4l6 2zm6.3 12.1l4.7 1.5V6.7L15 5v12.9l.3.2zM4 18.3L9 20V7.1L8.7 7 4 5.4v12.9z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M9 6l6-2 6 2v15l-6-2-6 2-6-2V4l6 2zm6.3 12.1l4.7 1.5V6.7L15 5v12.9l.3.2zM4 18.3L9 20V7.1L8.7 7 4 5.4v12.9z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M19 12c0-3.9-3.1-7-7-7s-7 3.1-7 7c0 1.4.4 2.6 1.1 3.7L12 23l5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7zm-7 4c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M19 12c0-3.9-3.1-7-7-7s-7 3.1-7 7c0 1.4.4 2.6 1.1 3.7L12 23l5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7zm-7 4c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M24 4h-4V0h-2v4h-4v2h4v4h2V6h4z"/>
<path d="M18 11h-1V7.1l-.1-.1H13V5.1c-.3-.1-.7-.1-1-.1-3.9 0-7 3.1-7 7 0 1.4.4 2.6 1.1 3.7L12 23l5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7 0-.3 0-.7-.1-1H18zm-6 5c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M24 4h-4V0h-2v4h-4v2h4v4h2V6h4z"/>
+ <path d="M18 11h-1V7.1l-.1-.1H13V5.1c-.3-.1-.7-.1-1-.1-3.9 0-7 3.1-7 7 0 1.4.4 2.6 1.1 3.7L12 23l5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7 0-.3 0-.7-.1-1H18zm-6 5c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M0 4h4V0h2v4h4v2H6v4H4V6H0z"/>
<path d="M6 11h1V7.1l.1-.1H11V5.1c.3-.1.7-.1 1-.1 3.9 0 7 3.1 7 7 0 1.4-.4 2.6-1.1 3.7L12 23l-5.9-7.3C5.4 14.6 5 13.4 5 12c0-.3 0-.7.1-1H6zm6 5c2.2 0 4-1.8 4-4s-1.8-4-4-4-4 1.8-4 4 1.8 4 4 4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M0 4h4V0h2v4h4v2H6v4H4V6H0z"/>
+ <path d="M6 11h1V7.1l.1-.1H11V5.1c.3-.1.7-.1 1-.1 3.9 0 7 3.1 7 7 0 1.4-.4 2.6-1.1 3.7L12 23l-5.9-7.3C5.4 14.6 5 13.4 5 12c0-.3 0-.7.1-1H6zm6 5c2.2 0 4-1.8 4-4s-1.8-4-4-4-4 1.8-4 4 1.8 4 4 4z"/>
+</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="markup">
<path id="left-bracket" d="M9.665 6.32l-4.259 4.274-1.406 1.406 1.406 1.406 4.259 4.274 1.406-1.438-4.259-4.243 4.259-4.243z"/>
<use transform="matrix(-1 0 0 1 24 0)" id="right-bracket" xlink:href="#left-bracket"/>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+ <g id="markup">
+ <path id="left-bracket" d="M9.665 6.32l-4.259 4.274-1.406 1.406 1.406 1.406 4.259 4.274 1.406-1.438-4.259-4.243 4.259-4.243z"/>
+ <use transform="matrix(-1 0 0 1 24 0)" id="right-bracket" xlink:href="#left-bracket"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="menu">
<path id="lines" d="M6 15h12a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1v-1a1 1 0 0 1 1-1zm-1-4v1a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-1a1 1 0 0 0-1-1H6a1 1 0 0 0-1 1zm0-5v1a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H6a1 1 0 0 0-1 1z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="menu">
+ <path id="lines" d="M6 15h12a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1v-1a1 1 0 0 1 1-1zm-1-4v1a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-1a1 1 0 0 0-1-1H6a1 1 0 0 0-1 1zm0-5v1a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H6a1 1 0 0 0-1 1z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M21 9c0-1.7-1.3-3-3-3H3v3l9 4 9-4zM3 11v6c0 1.7 1.3 3 3 3h15v-9l-9 4-9-4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M21 9c0-1.7-1.3-3-3-3H3v3l9 4 9-4zM3 11v6c0 1.7 1.3 3 3 3h15v-9l-9 4-9-4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M3 9c0-1.7 1.3-3 3-3h15v3l-9 4-9-4zm18 2v6c0 1.7-1.3 3-3 3H3v-9l9 4 9-4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M3 9c0-1.7 1.3-3 3-3h15v3l-9 4-9-4zm18 2v6c0 1.7-1.3 3-3 3H3v-9l9 4 9-4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M20 11l-4-3v2h-3V7h2l-3-4-3 4h2v3H8V8l-4 3 4 3v-2h3v3H9l3 4 3-4h-2v-3h3v2z"/>
</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="move-ltr">
<path id="arrow" d="M8.935 7.18l5.302 5.303-5.302 5.303L10.35 19.2l6.715-6.717-6.716-6.716z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="move-ltr">
+ <path id="arrow" d="M8.935 7.18l5.302 5.303-5.302 5.303L10.35 19.2l6.715-6.717-6.716-6.716z"/>
+ </g>
+</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M20 11l-4-3v2h-3V7h2l-3-4-3 4h2v3H8V8l-4 3 4 3v-2h3v3H9l3 4 3-4h-2v-3h3v2z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="move-rtl">
<path id="arrow" d="M15.065 17.786l-5.302-5.303 5.302-5.302-1.415-1.41-6.714 6.72 6.714 6.71z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="move-rtl">
+ <path id="arrow" d="M15.065 17.786l-5.302-5.303 5.302-5.302-1.415-1.41-6.714 6.72 6.714 6.71z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M12 5l2.5 2.5L11 11c-1.2 1.2-1.2 2.8 0 4l5.5-5.5L19 12V5h-7zm5 12H8c-.6 0-1-.4-1-1V7h3L8 5H5v11c0 1.7 1.3 3 3 3h11v-3l-2-2v3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M12 5l2.5 2.5L11 11c-1.2 1.2-1.2 2.8 0 4l5.5-5.5L19 12V5h-7zm5 12H8c-.6 0-1-.4-1-1V7h3L8 5H5v11c0 1.7 1.3 3 3 3h11v-3l-2-2v3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M12 5L9.5 7.5 13 11c1.2 1.2 1.2 2.8 0 4L7.5 9.5 5 12V5h7zM7 17h9c.6 0 1-.4 1-1V7h-3l2-2h3v11c0 1.7-1.3 3-3 3H5v-3l2-2v3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M12 5L9.5 7.5 13 11c1.2 1.2 1.2 2.8 0 4L7.5 9.5 5 12V5h7zM7 17h9c.6 0 1-.4 1-1V7h-3l2-2h3v11c0 1.7-1.3 3-3 3H5v-3l2-2v3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M17.8 5.7c-.5 0-.9.2-1.2.5s-.5.7-.5 1.2v4.3H11v-4l-6 5.5 6 5.5v-4h8v-9h-1.2z" id="line_return"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M17.8 5.7c-.5 0-.9.2-1.2.5s-.5.7-.5 1.2v4.3H11v-4l-6 5.5 6 5.5v-4h8v-9h-1.2z" id="line_return"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M6.2 5.7c.5 0 .9.2 1.2.5.3.3.5.7.5 1.2v4.3H13v-4l6 5.5-6 5.5v-4H5v-9h1.2z" id="line_return"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M6.2 5.7c.5 0 .9.2 1.2.5.3.3.5.7.5 1.2v4.3H13v-4l6 5.5-6 5.5v-4H5v-9h1.2z" id="line_return"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M6 7v12c-.6 0-1-.4-1-1V9H4v9c0 1.1.9 2 2 2h15V7H6zm9 11H8v-1h7v1zm0-2H8v-1h7v1zm0-2H8v-1h7v1zm4 4h-3v-5h3v5zm0-7H8V9h11v2z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M6 7v12c-.6 0-1-.4-1-1V9H4v9c0 1.1.9 2 2 2h15V7H6zm9 11H8v-1h7v1zm0-2H8v-1h7v1zm0-2H8v-1h7v1zm4 4h-3v-5h3v5zm0-7H8V9h11v2z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M19 7v12c.6 0 1-.4 1-1V9h1v9c0 1.1-.9 2-2 2H4V7h15zm-9 11h7v-1h-7v1zm0-2h7v-1h-7v1zm0-2h7v-1h-7v1zm-4 4h3v-5H6v5zm0-7h11V9H6v2z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M19 7v12c.6 0 1-.4 1-1V9h1v9c0 1.1-.9 2-2 2H4V7h15zm-9 11h7v-1h-7v1zm0-2h7v-1h-7v1zm0-2h7v-1h-7v1zm-4 4h3v-5H6v5zm0-7h11V9H6v2z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M15 13l2 2V5h-3v2h1zM3 3L2 4l1 1v14h3v-2H5V7l2 2v10h3v-2H9v-6l6 6h-1v2h3l3 3 1-1-3-3zm7 4V5H7l2 2zm8-2v2h1v10l2 2V5z" id="noWikiText-rtl"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M15 13l2 2V5h-3v2h1zM3 3L2 4l1 1v14h3v-2H5V7l2 2v10h3v-2H9v-6l6 6h-1v2h3l3 3 1-1-3-3zm7 4V5H7l2 2zm8-2v2h1v10l2 2V5z" id="noWikiText-rtl"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M9 13l-2 2V5h3v2H9zM21 3l1 1-1 1v14h-3v-2h1V7l-2 2v10h-3v-2h1v-6l-6 6h1v2H7l-3 3-1-1 3-3zm-7 4V5h3l-2 2zM6 5v2H5v10l-2 2V5z" id="noWikiText-rtl"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M9 13l-2 2V5h3v2H9zM21 3l1 1-1 1v14h-3v-2h1V7l-2 2v10h-3v-2h1v-6l-6 6h1v2H7l-3 3-1-1 3-3zm-7 4V5h3l-2 2zM6 5v2H5v10l-2 2V5z" id="noWikiText-rtl"/>
+</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" id="svg3116"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" id="svg3116"><style>* { fill: #fff }</style>
<path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm-1-5h2V8h-2zm0 3h2v-2h-2z" id="alert"/>
</svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" id="svg3116"><style>* { fill: #36c }</style>
+ <path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm-1-5h2V8h-2zm0 3h2v-2h-2z" id="alert"/>
+</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #ffffff }</style>
+<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #fff }</style>
<path d="M17.8 18.6H2.5l2.7-2.7V6h15.3v9.9c0 1.53-1.17 2.7-2.7 2.7zm-7.542-4.95c0 .405-.135.675-.405.945-.27.27-.607.405-.945.405-.405 0-.675-.135-.945-.405a1.332 1.332 0 0 1-.405-.945c0-.338.135-.675.405-.945.27-.27.608-.405.945-.405.338 0 .675.135.945.405.27.27.405.607.405.945zm4.05 0c0 .405-.135.675-.405.945-.27.27-.607.405-.945.405-.405 0-.675-.135-.945-.405a1.332 1.332 0 0 1-.405-.945c0-.338.135-.675.405-.945.27-.27.608-.405.945-.405.338 0 .675.135.945.405.27.27.405.607.405.945zm4.05 0c0 .405-.135.675-.405.945-.27.27-.607.405-.945.405-.405 0-.675-.135-.945-.405a1.332 1.332 0 0 1-.405-.945c0-.338.135-.675.405-.945.27-.27.608-.405.945-.405.338 0 .675.135.945.405.27.27.405.607.405.945z" id="ongoing-conversation" fill-rule="evenodd"/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #347bff }</style>
+<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #36c }</style>
<path d="M17.8 18.6H2.5l2.7-2.7V6h15.3v9.9c0 1.53-1.17 2.7-2.7 2.7zm-7.542-4.95c0 .405-.135.675-.405.945-.27.27-.607.405-.945.405-.405 0-.675-.135-.945-.405a1.332 1.332 0 0 1-.405-.945c0-.338.135-.675.405-.945.27-.27.608-.405.945-.405.338 0 .675.135.945.405.27.27.405.607.405.945zm4.05 0c0 .405-.135.675-.405.945-.27.27-.607.405-.945.405-.405 0-.675-.135-.945-.405a1.332 1.332 0 0 1-.405-.945c0-.338.135-.675.405-.945.27-.27.608-.405.945-.405.338 0 .675.135.945.405.27.27.405.607.405.945zm4.05 0c0 .405-.135.675-.405.945-.27.27-.607.405-.945.405-.405 0-.675-.135-.945-.405a1.332 1.332 0 0 1-.405-.945c0-.338.135-.675.405-.945.27-.27.608-.405.945-.405.338 0 .675.135.945.405.27.27.405.607.405.945z" id="ongoing-conversation" fill-rule="evenodd"/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #ffffff }</style>
+<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #fff }</style>
<path d="M5.2 18.6h15.3l-2.7-2.7V6H2.5v9.9c0 1.53 1.17 2.7 2.7 2.7zm7.542-4.95c0 .405.135.675.405.945.27.27.607.405.945.405.405 0 .675-.135.945-.405.27-.27.405-.607.405-.945 0-.337-.135-.675-.405-.945a1.334 1.334 0 0 0-.945-.405c-.338 0-.675.135-.945.405-.27.27-.405.607-.405.945zm-4.05 0c0 .405.135.675.405.945.27.27.608.405.945.405.405 0 .675-.135.945-.405.27-.27.405-.607.405-.945 0-.337-.135-.675-.405-.945a1.334 1.334 0 0 0-.945-.405c-.338 0-.675.135-.945.405-.27.27-.405.608-.405.945zm-4.05 0c0 .405.135.675.405.945.27.27.608.405.945.405.405 0 .675-.135.945-.405.27-.27.405-.607.405-.945 0-.337-.135-.675-.405-.945a1.332 1.332 0 0 0-.945-.405c-.337 0-.675.135-.945.405-.27.27-.405.608-.405.945z" id="ongoing-conversation" fill-rule="evenodd"/>
</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #347bff }</style>
+<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>* { fill: #36c }</style>
<path d="M5.2 18.6h15.3l-2.7-2.7V6H2.5v9.9c0 1.53 1.17 2.7 2.7 2.7zm7.542-4.95c0 .405.135.675.405.945.27.27.607.405.945.405.405 0 .675-.135.945-.405.27-.27.405-.607.405-.945 0-.337-.135-.675-.405-.945a1.334 1.334 0 0 0-.945-.405c-.338 0-.675.135-.945.405-.27.27-.405.607-.405.945zm-4.05 0c0 .405.135.675.405.945.27.27.608.405.945.405.405 0 .675-.135.945-.405.27-.27.405-.607.405-.945 0-.337-.135-.675-.405-.945a1.334 1.334 0 0 0-.945-.405c-.338 0-.675.135-.945.405-.27.27-.405.608-.405.945zm-4.05 0c0 .405.135.675.405.945.27.27.608.405.945.405.405 0 .675-.135.945-.405.27-.27.405-.607.405-.945 0-.337-.135-.675-.405-.945a1.332 1.332 0 0 0-.945-.405c-.337 0-.675.135-.945.405-.27.27-.405.608-.405.945z" id="ongoing-conversation" fill-rule="evenodd"/>
</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M10 8h9v2h-9V8zm0 3h9v2h-9v-2zm0 3h6v2h-6v-2zm11-8H3V4h18v2zm0 14H3v-2h18v2zM3 12l5 4V8l-5 4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M10 8h9v2h-9V8zm0 3h9v2h-9v-2zm0 3h6v2h-6v-2zm11-8H3V4h18v2zm0 14H3v-2h18v2zM3 12l5 4V8l-5 4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M14 8H5v2h9V8zm0 3H5v2h9v-2zm0 3H8v2h6v-2zM3 6h18V4H3v2zm0 14h18v-2H3v2zm18-8l-5 4V8l5 4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M14 8H5v2h9V8zm0 3H5v2h9v-2zm0 3H8v2h6v-2zM3 6h18V4H3v2zm0 14h18v-2H3v2zm18-8l-5 4V8l5 4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="outline-ltr">
<path id="text" d="M5 13h14v6H5v-6z"/>
<path id="float" d="M5 5v6h6V5H5zm5 5H6V6h4v4z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="outline-ltr">
+ <path id="text" d="M5 13h14v6H5v-6z"/>
+ <path id="float" d="M5 5v6h6V5H5zm5 5H6V6h4v4z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="outline-rtl">
<path id="text" d="M19 19H5v-6h14v6z"/>
<path id="float" d="M13 5v6h6V5h-6zm1 1h4v4h-4V6z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="outline-rtl">
+ <path id="text" d="M19 19H5v-6h14v6z"/>
+ <path id="float" d="M13 5v6h6V5h-6zm1 1h4v4h-4V6z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm-2 12V9l6 4-6 4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm-2 12V9l6 4-6 4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M12 5c4.4 0 8 3.6 8 8s-3.6 8-8 8-8-3.6-8-8 3.6-8 8-8zm2 12V9l-6 4 6 4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M12 5c4.4 0 8 3.6 8 8s-3.6 8-8 8-8-3.6-8-8 3.6-8 8-8zm2 12V9l-6 4 6 4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M18 8h-1V4H7v4H3v6c0 1.7 1.3 3 3 3h1v3h10v-3h4v-6c0-1.7-1.3-3-3-3zM8 5h8v3H8V5zm8 14H8v-6h8v6z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M18 8h-1V4H7v4H3v6c0 1.7 1.3 3 3 3h1v3h10v-3h4v-6c0-1.7-1.3-3-3-3zM8 5h8v3H8V5zm8 14H8v-6h8v6z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M6 8h1V4h10v4h4v6c0 1.7-1.3 3-3 3h-1v3H7v-3H3v-6c0-1.7 1.3-3 3-3zm10-3H8v3h8V5zM8 19h8v-6H8v6z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M6 8h1V4h10v4h4v6c0 1.7-1.3 3-3 3h-1v3H7v-3H3v-6c0-1.7 1.3-3 3-3zm10-3H8v3h8V5zM8 19h8v-6H8v6z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M18 9.9c-.7 0-1.4.3-1.8.9V6h-4c.2-.4.4-.8.4-1.2 0-1.2-1-2.2-2.2-2.2-1.3-.1-2.3.9-2.3 2.2 0 .4.2.8.4 1.2H4.1v3.6l.6-.1c1.4 0 2.5 1.1 2.5 2.5s-1.1 2.5-2.5 2.5c-.2 0-.4 0-.6-.1V18H9c-.5.4-.9 1-.9 1.8 0 1.2 1 2.2 2.3 2.2 1.2 0 2.2-1 2.2-2.2 0-.7-.3-1.4-.9-1.8h4.5v-4.5c.4.5 1 .9 1.8.9 1.2 0 2.2-1 2.2-2.2 0-1.3-1-2.3-2.2-2.3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M18 9.9c-.7 0-1.4.3-1.8.9V6h-4c.2-.4.4-.8.4-1.2 0-1.2-1-2.2-2.2-2.2-1.3-.1-2.3.9-2.3 2.2 0 .4.2.8.4 1.2H4.1v3.6l.6-.1c1.4 0 2.5 1.1 2.5 2.5s-1.1 2.5-2.5 2.5c-.2 0-.4 0-.6-.1V18H9c-.5.4-.9 1-.9 1.8 0 1.2 1 2.2 2.3 2.2 1.2 0 2.2-1 2.2-2.2 0-.7-.3-1.4-.9-1.8h4.5v-4.5c.4.5 1 .9 1.8.9 1.2 0 2.2-1 2.2-2.2 0-1.3-1-2.3-2.2-2.3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M6.3 9.9c.7 0 1.4.3 1.8.9V6h4c-.2-.4-.4-.8-.4-1.2 0-1.2 1-2.2 2.2-2.2 1.3-.1 2.3.9 2.3 2.2 0 .4-.2.8-.4 1.2h4.4v3.6l-.6-.1c-1.4 0-2.5 1.1-2.5 2.5s1.1 2.5 2.5 2.5c.2 0 .4 0 .6-.1V18h-4.9c.5.4.9 1 .9 1.8 0 1.2-1 2.2-2.3 2.2-1.2 0-2.2-1-2.2-2.2 0-.7.3-1.4.9-1.8H8.1v-4.5c-.4.5-1 .9-1.8.9-1.2 0-2.2-1-2.2-2.2 0-1.3 1-2.3 2.2-2.3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M6.3 9.9c.7 0 1.4.3 1.8.9V6h4c-.2-.4-.4-.8-.4-1.2 0-1.2 1-2.2 2.2-2.2 1.3-.1 2.3.9 2.3 2.2 0 .4-.2.8-.4 1.2h4.4v3.6l-.6-.1c-1.4 0-2.5 1.1-2.5 2.5s1.1 2.5 2.5 2.5c.2 0 .4 0 .6-.1V18h-4.9c.5.4.9 1 .9 1.8 0 1.2-1 2.2-2.3 2.2-1.2 0-2.2-1-2.2-2.2 0-.7.3-1.4.9-1.8H8.1v-4.5c-.4.5-1 .9-1.8.9-1.2 0-2.2-1-2.2-2.2 0-1.3 1-2.3 2.2-2.3z"/>
+</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="quotes">
<path id="quote" d="M6.9 8.4c-.446.55-1.974 2.6-1.9 5.7V17h4.7c.9 0 1.6-.7 1.6-1.6V11H8.2s.05-.74.6-1.4c.453-.543 1-.9 1.6-1.2.2-.1.47-.212.6-.5.127-.282.2-.5.2-.9v-.6c-1 .2-1.744.197-2.6.6-.856.403-1.272.873-1.7 1.4z"/>
</g>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+ <g id="quotes">
+ <path id="quote" d="M6.9 8.4c-.446.55-1.974 2.6-1.9 5.7V17h4.7c.9 0 1.6-.7 1.6-1.6V11H8.2s.05-.74.6-1.4c.453-.543 1-.9 1.6-1.2.2-.1.47-.212.6-.5.127-.282.2-.5.2-.9v-.6c-1 .2-1.744.197-2.6.6-.856.403-1.272.873-1.7 1.4z"/>
+ </g>
+ <use transform="translate(8)" id="quote2" width="24" height="24" xlink:href="#quote"/>
+</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="quotes">
<path id="quote" d="M17.1 8.4c.446.55 1.9 2.6 1.9 5.7V17h-4.7c-.9 0-1.6-.7-1.6-1.6V11h3.1s-.05-.74-.6-1.4c-.453-.543-1-.9-1.6-1.2-.2-.1-.47-.212-.6-.5-.127-.282-.2-.5-.2-.9v-.6c1 .2 1.744.197 2.6.6.856.403 1.272.873 1.7 1.4z"/>
</g>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+ <g id="quotes">
+ <path id="quote" d="M17.1 8.4c.446.55 1.9 2.6 1.9 5.7V17h-4.7c-.9 0-1.6-.7-1.6-1.6V11h3.1s-.05-.74-.6-1.4c-.453-.543-1-.9-1.6-1.2-.2-.1-.47-.212-.6-.5-.127-.282-.2-.5-.2-.9v-.6c1 .2 1.744.197 2.6.6.856.403 1.272.873 1.7 1.4z"/>
+ </g>
+ <use transform="translate(-8)" id="quote2" width="24" height="24" xlink:href="#quote"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="quotes-add">
<path id="quote" d="M5.9 10.4c-.446.55-1.974 2.6-1.9 5.7V19h4.7c.9 0 1.593-.7 1.6-1.6V13H7.2s.05-.74.6-1.4c.453-.543 1-.9 1.6-1.2.2-.1.47-.212.6-.5.127-.282.2-.5.2-.9v-.6c-1 .2-1.744.197-2.6.6-.856.403-1.272.873-1.7 1.4z"/>
<path id="quote2" d="M15 9.344c-.476.32-.78.677-1.094 1.062A8.76 8.76 0 0 0 12 16.094V19h4.688a1.6 1.6 0 0 0 1.625-1.594V13H15V9.344z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="quotes-add">
+ <path id="quote" d="M5.9 10.4c-.446.55-1.974 2.6-1.9 5.7V19h4.7c.9 0 1.593-.7 1.6-1.6V13H7.2s.05-.74.6-1.4c.453-.543 1-.9 1.6-1.2.2-.1.47-.212.6-.5.127-.282.2-.5.2-.9v-.6c-1 .2-1.744.197-2.6.6-.856.403-1.272.873-1.7 1.4z"/>
+ <path id="quote2" d="M15 9.344c-.476.32-.78.677-1.094 1.062A8.76 8.76 0 0 0 12 16.094V19h4.688a1.6 1.6 0 0 0 1.625-1.594V13H15V9.344z"/>
+ <path id="add" d="M18 6V2h-2v4h-4v2h4v4h2V8h4V6z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="quotes-add">
<path id="quote" d="M18.097 10.4c.446.55 1.974 2.6 1.9 5.7V19h-4.7c-.9 0-1.593-.7-1.6-1.6V13h3.1s-.05-.74-.6-1.4c-.453-.543-1-.9-1.6-1.2-.2-.1-.47-.212-.6-.5-.127-.282-.2-.5-.2-.9v-.6c1 .2 1.744.197 2.6.6.856.403 1.272.873 1.7 1.4z"/>
<path id="quote2" d="M8.997 9.344c.476.32.782.677 1.094 1.062A8.758 8.758 0 0 1 12 16.094V19H7.31c-.9 0-1.618-.694-1.625-1.594V13h3.312V9.344z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="quotes-add">
+ <path id="quote" d="M18.097 10.4c.446.55 1.974 2.6 1.9 5.7V19h-4.7c-.9 0-1.593-.7-1.6-1.6V13h3.1s-.05-.74-.6-1.4c-.453-.543-1-.9-1.6-1.2-.2-.1-.47-.212-.6-.5-.127-.282-.2-.5-.2-.9v-.6c1 .2 1.744.197 2.6.6.856.403 1.272.873 1.7 1.4z"/>
+ <path id="quote2" d="M8.997 9.344c.476.32.782.677 1.094 1.062A8.758 8.758 0 0 1 12 16.094V19H7.31c-.9 0-1.618-.694-1.625-1.594V13h3.312V9.344z"/>
+ <path id="add" d="M5.997 6V2h2v4h4v2h-4v4h-2V8h-4V6z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="regular-expression">
<path id="left-bracket" d="M3 12.045c0-.99.15-1.915.45-2.777A6.886 6.886 0 0 1 4.764 7H6.23a7.923 7.923 0 0 0-1.25 2.374 8.563 8.563 0 0 0 .007 5.314c.29.85.7 1.622 1.23 2.312h-1.45a6.53 6.53 0 0 1-1.314-2.223 8.126 8.126 0 0 1-.45-2.732"/>
<path id="dot" d="M10 16a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="regular-expression">
+ <path id="left-bracket" d="M3 12.045c0-.99.15-1.915.45-2.777A6.886 6.886 0 0 1 4.764 7H6.23a7.923 7.923 0 0 0-1.25 2.374 8.563 8.563 0 0 0 .007 5.314c.29.85.7 1.622 1.23 2.312h-1.45a6.53 6.53 0 0 1-1.314-2.223 8.126 8.126 0 0 1-.45-2.732"/>
+ <path id="dot" d="M10 16a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/>
+ <path id="star" d="M14.25 7.013l-.24 2.156 2.187-.61.193 1.47-1.992.14 1.307 1.74-1.33.71-.914-1.833-.8 1.822-1.38-.698 1.296-1.74-1.98-.152.23-1.464 2.14.61-.24-2.158h1.534"/>
+ <path id="right-bracket" d="M21 12.045c0 .982-.152 1.896-.457 2.744A6.51 6.51 0 0 1 19.236 17h-1.453a8.017 8.017 0 0 0 1.225-2.31c.29-.855.434-1.74.434-2.66 0-.91-.14-1.797-.422-2.66a7.913 7.913 0 0 0-1.248-2.374h1.465a6.764 6.764 0 0 1 1.313 2.28c.3.86.45 1.782.45 2.764"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<circle cx="11.5" cy="8.5" r="2.5"/>
<path d="M16.3 8.7L17 8l-.8-.8.4-.8-1.1-.5.1-.9-1.2-.2-.1-.9-1.2.2-.4-.8-1.1.5L11 3l-.8.8-.9-.4-.5 1.1-.9-.2-.2 1.2-.9.2.2 1.2-.9.4.5 1.1L6 9l.8.8-.4.8 1.1.5-.1.9 1.2.2.1.9 1.2-.2.4.8 1.1-.5.6.8.8-.8.8.4.5-1.1.9.1.2-1.2.9-.1-.2-1.2.8-.4-.4-1zM11.5 12C9.6 12 8 10.4 8 8.5S9.6 5 11.5 5 15 6.6 15 8.5 13.4 12 11.5 12zm.5 3l-.7-.7-1.1.6-.4-.7-.8.3V23l2.5-3 2.5 3v-8.5l-1-.5z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <circle cx="11.5" cy="8.5" r="2.5"/>
+ <path d="M16.3 8.7L17 8l-.8-.8.4-.8-1.1-.5.1-.9-1.2-.2-.1-.9-1.2.2-.4-.8-1.1.5L11 3l-.8.8-.9-.4-.5 1.1-.9-.2-.2 1.2-.9.2.2 1.2-.9.4.5 1.1L6 9l.8.8-.4.8 1.1.5-.1.9 1.2.2.1.9 1.2-.2.4.8 1.1-.5.6.8.8-.8.8.4.5-1.1.9.1.2-1.2.9-.1-.2-1.2.8-.4-.4-1zM11.5 12C9.6 12 8 10.4 8 8.5S9.6 5 11.5 5 15 6.6 15 8.5 13.4 12 11.5 12zm.5 3l-.7-.7-1.1.6-.4-.7-.8.3V23l2.5-3 2.5 3v-8.5l-1-.5z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="search">
<path id="magnifying-glass" d="M10.5 4a6.5 6.5 0 1 0 2.844 12.344L16 19c1.4 1.4 2.5 1.5 4 0l-4.438-4.438A6.426 6.426 0 0 0 17 10.5 6.5 6.5 0 0 0 10.5 4zm0 2a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="search">
+ <path id="magnifying-glass" d="M10.5 4a6.5 6.5 0 1 0 2.844 12.344L16 19c1.4 1.4 2.5 1.5 4 0l-4.438-4.438A6.426 6.426 0 0 0 17 10.5 6.5 6.5 0 0 0 10.5 4zm0 2a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="search">
<path id="magnifying-glass" d="M13.5 4a6.5 6.5 0 1 1-2.844 12.344L8 19c-1.4 1.4-2.5 1.5-4 0l4.438-4.438A6.426 6.426 0 0 1 7 10.5 6.5 6.5 0 0 1 13.5 4zm0 2a4.5 4.5 0 1 0 0 9 4.5 4.5 0 0 0 0-9z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="search">
+ <path id="magnifying-glass" d="M13.5 4a6.5 6.5 0 1 1-2.844 12.344L8 19c-1.4 1.4-2.5 1.5-4 0l4.438-4.438A6.426 6.426 0 0 1 7 10.5 6.5 6.5 0 0 1 13.5 4zm0 2a4.5 4.5 0 1 0 0 9 4.5 4.5 0 0 0 0-9z"/>
+ </g>
+</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #fff }</style>
<g id="secure">
<path id="lock" d="M8 5h.02v-.997c0-.057.003-1.41-.833-2.255-.434-.438-.998-.66-1.68-.66s-1.244.222-1.677.66c-.837.846-.833 2.198-.832 2.25V5H3a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1zM3.998 5V3.993c0-.01.005-1 .543-1.543.49-.485 1.45-.487 1.94-.002.543.546.545 1.536.545 1.55V5H3.998z"/>
</g>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #36c }</style>
+ <g id="secure">
+ <path id="lock" d="M8 5h.02v-.997c0-.057.003-1.41-.833-2.255-.434-.438-.998-.66-1.68-.66s-1.244.222-1.677.66c-.837.846-.833 2.198-.832 2.25V5H3a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1zM3.998 5V3.993c0-.01.005-1 .543-1.543.49-.485 1.45-.487 1.94-.002.543.546.545 1.536.545 1.55V5H3.998z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="settings">
<path id="gear" d="M3 4h3v2H3zm9 0h9v2h-9zM8 3h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm-5 8h9v2H3zm15 0h3v2h-3zm-4-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1zM3 18h6v2H3zm12 0h6v2h-6zm-4-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1z"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="settings">
+ <path id="gear" d="M3 4h3v2H3zm9 0h9v2h-9zM8 3h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm-5 8h9v2H3zm15 0h3v2h-3zm-4-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1zM3 18h6v2H3zm12 0h6v2h-6zm-4-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M0 20h24v1H0v-1zm6-8l-1-1-2 2-2-2-1 1 2 2-2 2 1 1 2-2 2 2 1-1-2-2zm15.6 3.7c-.9-.5-1.9-.5-2.7 0-1.5.9-3.1.4-3.1.4-.4-.2-.8-.4-1.1-.6 2.2-.6 4.4-1.8 6-3.9 1.1-1.2 2.5-3.9.4-6-.7-.7-1.6-1.1-2.7-1-1.4.1-2.8.9-3.9 2.1-.9 1.1-3.1 4.5-2.3 7.5 0 .1 0 .2.1.3-2.3.3-4.2.2-4.4.1v1.5c.7.1 2.7.2 5.1-.2.5.7 1.3 1.2 2.3 1.6.1 0 2.4.8 4.5-.6.5-.3.9-.1 1.1 0 .4.2.7.6.7 1H23c0-.8-.6-1.7-1.4-2.2zm-8-1.7c-.5-2.2 1.1-5.1 2-6.2.8-.9 1.8-1.5 2.8-1.6h.1c.6 0 1.1.2 1.5.6 1.6 1.6-.4 3.9-.5 4-1.5 2-3.7 3-5.8 3.5l-.1-.3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M0 20h24v1H0v-1zm6-8l-1-1-2 2-2-2-1 1 2 2-2 2 1 1 2-2 2 2 1-1-2-2zm15.6 3.7c-.9-.5-1.9-.5-2.7 0-1.5.9-3.1.4-3.1.4-.4-.2-.8-.4-1.1-.6 2.2-.6 4.4-1.8 6-3.9 1.1-1.2 2.5-3.9.4-6-.7-.7-1.6-1.1-2.7-1-1.4.1-2.8.9-3.9 2.1-.9 1.1-3.1 4.5-2.3 7.5 0 .1 0 .2.1.3-2.3.3-4.2.2-4.4.1v1.5c.7.1 2.7.2 5.1-.2.5.7 1.3 1.2 2.3 1.6.1 0 2.4.8 4.5-.6.5-.3.9-.1 1.1 0 .4.2.7.6.7 1H23c0-.8-.6-1.7-1.4-2.2zm-8-1.7c-.5-2.2 1.1-5.1 2-6.2.8-.9 1.8-1.5 2.8-1.6h.1c.6 0 1.1.2 1.5.6 1.6 1.6-.4 3.9-.5 4-1.5 2-3.7 3-5.8 3.5l-.1-.3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M24 20H0v1h24v-1zm-6-8l1-1 2 2 2-2 1 1-2 2 2 2-1 1-2-2-2 2-1-1 2-2zM2.4 15.7c.9-.5 1.9-.5 2.7 0 1.5.9 3.1.4 3.1.4.4-.2.8-.4 1.1-.6-2.2-.6-4.4-1.8-6-3.9-1.1-1.2-2.5-3.9-.4-6 .7-.7 1.6-1.1 2.7-1 1.4.1 2.8.9 3.9 2.1.9 1.1 3.1 4.5 2.3 7.5 0 .1 0 .2-.1.3 2.3.3 4.2.2 4.4.1v1.5c-.7.1-2.7.2-5.1-.2-.5.7-1.3 1.2-2.3 1.6-.1 0-2.4.8-4.5-.6-.5-.3-.9-.1-1.1 0-.4.2-.7.6-.7 1H1c0-.8.6-1.7 1.4-2.2zm8-1.7c.5-2.2-1.1-5.1-2-6.2-.8-.9-1.8-1.5-2.8-1.6h-.1c-.6 0-1.1.2-1.5.6-1.6 1.6.4 3.9.5 4 1.5 2 3.7 3 5.8 3.5l.1-.3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M24 20H0v1h24v-1zm-6-8l1-1 2 2 2-2 1 1-2 2 2 2-1 1-2-2-2 2-1-1 2-2zM2.4 15.7c.9-.5 1.9-.5 2.7 0 1.5.9 3.1.4 3.1.4.4-.2.8-.4 1.1-.6-2.2-.6-4.4-1.8-6-3.9-1.1-1.2-2.5-3.9-.4-6 .7-.7 1.6-1.1 2.7-1 1.4.1 2.8.9 3.9 2.1.9 1.1 3.1 4.5 2.3 7.5 0 .1 0 .2-.1.3 2.3.3 4.2.2 4.4.1v1.5c-.7.1-2.7.2-5.1-.2-.5.7-1.3 1.2-2.3 1.6-.1 0-2.4.8-4.5-.6-.5-.3-.9-.1-1.1 0-.4.2-.7.6-.7 1H1c0-.8.6-1.7 1.4-2.2zm8-1.7c.5-2.2-1.1-5.1-2-6.2-.8-.9-1.8-1.5-2.8-1.6h-.1c-.6 0-1.1.2-1.5.6-1.6 1.6.4 3.9.5 4 1.5 2 3.7 3 5.8 3.5l.1-.3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path id="a" d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z"/>
<g id="down">
<path id="arrow" d="M22 3l-3.5 6L15 3z"/>
--- /dev/null
+<?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: #36c }</style>
+ <path id="a" d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z"/>
+ <g id="down">
+ <path id="arrow" d="M22 3l-3.5 6L15 3z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path id="a" d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z"/>
<g id="down">
<path id="arrow" d="M9 3L5.5 9 2 3z"/>
--- /dev/null
+<?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: #36c }</style>
+ <path id="a" d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z"/>
+ <g id="down">
+ <path id="arrow" d="M9 3L5.5 9 2 3z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="special-character">
<path id="omega" d="M12 6.708c-.794 0-1.368.103-1.894.31-.525.207-.944.496-1.255.867-.31.366-.53.808-.66 1.327a7.232 7.232 0 0 0-.19 1.7c0 .512.06 1 .18 1.46.12.46.31.87.567 1.23.63.862 1.156 1.138 2.012 1.362L11 18H6v-3h.604l.53 1.353.395.053.6.044.75.035.455.01H10l-.09-.895c-.63-.094-.812-.268-1.337-.522-.525-.26-.98-.59-1.365-.99a4.428 4.428 0 0 1-.89-1.4 4.78 4.78 0 0 1-.32-1.778c0-.82.13-1.537.394-2.15a3.97 3.97 0 0 1 1.163-1.54c.507-.407 1.133-.71 1.878-.912.745-.206 1.6-.31 2.565-.31.96 0 1.81.103 2.556.31.75.2 1.38.504 1.887.912.51.407.9.92 1.16 1.54.27.614.404 1.33.404 2.15a4.79 4.79 0 0 1-.32 1.78 4.35 4.35 0 0 1-.9 1.397c-.38.4-.83.732-1.355.99-.526.255-.708.43-1.337.523l-.092.894h.66l.448-.01.75-.034.606-.044.4-.053.534-1.354H18v3h-5l.246-3.04c1.066-.11 1.337-.698 2.002-1.365.263-.36.452-.77.568-1.23.122-.46.183-.947.183-1.46 0-.62-.07-1.186-.198-1.7a3.175 3.175 0 0 0-.66-1.326c-.31-.37-.73-.66-1.255-.867-.525-.206-1.1-.31-1.894-.31"/>
</g>
--- /dev/null
+<?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: #36c }</style>
+ <g id="special-character">
+ <path id="omega" d="M12 6.708c-.794 0-1.368.103-1.894.31-.525.207-.944.496-1.255.867-.31.366-.53.808-.66 1.327a7.232 7.232 0 0 0-.19 1.7c0 .512.06 1 .18 1.46.12.46.31.87.567 1.23.63.862 1.156 1.138 2.012 1.362L11 18H6v-3h.604l.53 1.353.395.053.6.044.75.035.455.01H10l-.09-.895c-.63-.094-.812-.268-1.337-.522-.525-.26-.98-.59-1.365-.99a4.428 4.428 0 0 1-.89-1.4 4.78 4.78 0 0 1-.32-1.778c0-.82.13-1.537.394-2.15a3.97 3.97 0 0 1 1.163-1.54c.507-.407 1.133-.71 1.878-.912.745-.206 1.6-.31 2.565-.31.96 0 1.81.103 2.556.31.75.2 1.38.504 1.887.912.51.407.9.92 1.16 1.54.27.614.404 1.33.404 2.15a4.79 4.79 0 0 1-.32 1.78 4.35 4.35 0 0 1-.9 1.397c-.38.4-.83.732-1.355.99-.526.255-.708.43-1.337.523l-.092.894h.66l.448-.01.75-.034.606-.044.4-.053.534-1.354H18v3h-5l.246-3.04c1.066-.11 1.337-.698 2.002-1.365.263-.36.452-.77.568-1.23.122-.46.183-.947.183-1.46 0-.62-.07-1.186-.198-1.7a3.175 3.175 0 0 0-.66-1.326c-.31-.37-.73-.66-1.255-.867-.525-.206-1.1-.31-1.894-.31"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M19 20H2l3-3V6h17v11c0 1.7-1.3 3-3 3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M19 20H2l3-3V6h17v11c0 1.7-1.3 3-3 3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M5 20h17l-3-3V6H2v11c0 1.7 1.3 3 3 3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M5 20h17l-3-3V6H2v11c0 1.7 1.3 3 3 3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M5 6v11l-3 3h17c1.7 0 3-1.3 3-3V6H5zm8 3h1v3h3v1h-3v3h-1v-3h-3v-1h3V9z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M5 6v11l-3 3h17c1.7 0 3-1.3 3-3V6H5zm8 3h1v3h3v1h-3v3h-1v-3h-3v-1h3V9z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M2 6v11c0 1.7 1.3 3 3 3h17l-3-3V6H2zm8 3h1v3h3v1h-3v3h-1v-3H7v-1h3V9z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M2 6v11c0 1.7 1.3 3 3 3h17l-3-3V6H2zm8 3h1v3h3v1h-3v3h-1v-3H7v-1h3V9z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M20 9v9l2 2H8V9h12zM3 4h12v4H7v7H1l2-2V4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M20 9v9l2 2H8V9h12zM3 4h12v4H7v7H1l2-2V4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M3 9v9l-2 2h14V9H3zm17-5H8v4h8v7h6l-2-2V4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M3 9v9l-2 2h14V9H3zm17-5H8v4h8v7h6l-2-2V4z"/>
+</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: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
<path d="M12 7.4l1.7 3.6 4 .5-2.7 2.8.5 3.9-3.5-1.7-3.6 1.7.6-3.9-2.8-2.8 3.9-.5L12 7.4M12 4L9.2 9.6l-6.2.9 4.5 4.4L6.4 21l5.6-3 5.5 3-1-6.2 4.5-4.4-6.3-.9L12 4z"/>
</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M12 7.4l1.7 3.6 4 .5-2.7 2.8.5 3.9-3.5-1.7-3.6 1.7.6-3.9-2.8-2.8 3.9-.5L12 7.4M12 4L9.2 9.6l-6.2.9 4.5 4.4L6.4 21l5.6-3 5.5 3-1-6.2 4.5-4.4-6.3-.9L12 4z"/>
</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: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
<path d="M12 7.4l1.7 3.6 4 .5-2.7 2.8.5 3.9-3.5-1.7-3.6 1.7.6-3.9-2.8-2.8 3.9-.5L12 7.4M12 4L9.2 9.6l-6.2.9 4.5 4.4L6.4 21l5.6-3 5.5 3-1-6.2 4.5-4.4-6.3-.9L12 4z"/>
</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3 11.1H9v-6h6v6z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3 11.1H9v-6h6v6z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="strikethrough-a">
<path id="strikethrough" d="M6 11h12v1H6v-1z"/>
<path id="a" d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="strikethrough-a">
+ <path id="strikethrough" d="M6 11h12v1H6v-1z"/>
+ <path id="a" d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="strikethrough-s">
<path id="strikethrough" d="M6 12h12v1H6v-1z"/>
<path id="s" d="M12.094 6c-1.133 0-2.076.287-2.75.9-.67.613-1 1.49-1 2.52 0 .89.22 1.602.72 2.13.497.528 1.278.91 2.31 1.14l.813.182v-.03c.656.147 1.128.375 1.375.63.252.256.375.607.375 1.11 0 .573-.172.97-.53 1.26-.36.29-.895.45-1.626.45-.47 0-.962-.074-1.462-.24a7.288 7.288 0 0 1-1.562-.75l-.374-.238v2.158l.156.062c.58.237 1.144.417 1.69.54.548.12 1.07.18 1.56.18 1.287 0 2.298-.293 3-.9.71-.605 1.063-1.486 1.063-2.608 0-.943-.256-1.726-.78-2.312-.522-.592-1.306-1-2.345-1.23l-.812-.18c-.714-.148-1.202-.352-1.404-.54-.206-.202-.313-.484-.313-.934 0-.533.162-.9.5-1.17.342-.27.836-.42 1.53-.42.396 0 .82.052 1.25.18.434.128.91.334 1.407.6l.375.18V6.63s-1.19-.383-1.69-.48c-.5-.097-.983-.15-1.467-.15z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="strikethrough-s">
+ <path id="strikethrough" d="M6 12h12v1H6v-1z"/>
+ <path id="s" d="M12.094 6c-1.133 0-2.076.287-2.75.9-.67.613-1 1.49-1 2.52 0 .89.22 1.602.72 2.13.497.528 1.278.91 2.31 1.14l.813.182v-.03c.656.147 1.128.375 1.375.63.252.256.375.607.375 1.11 0 .573-.172.97-.53 1.26-.36.29-.895.45-1.626.45-.47 0-.962-.074-1.462-.24a7.288 7.288 0 0 1-1.562-.75l-.374-.238v2.158l.156.062c.58.237 1.144.417 1.69.54.548.12 1.07.18 1.56.18 1.287 0 2.298-.293 3-.9.71-.605 1.063-1.486 1.063-2.608 0-.943-.256-1.726-.78-2.312-.522-.592-1.306-1-2.345-1.23l-.812-.18c-.714-.148-1.202-.352-1.404-.54-.206-.202-.313-.484-.313-.934 0-.533.162-.9.5-1.17.342-.27.836-.42 1.53-.42.396 0 .82.052 1.25.18.434.128.91.334 1.407.6l.375.18V6.63s-1.19-.383-1.69-.48c-.5-.097-.983-.15-1.467-.15z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="strikethrough-y">
<path id="strikethrough" d="M6 11h12v1H6v-1z"/>
<path id="a" d="M7 6h1.724l3.288 4.935L15.276 6H17l-4.194 6.285V18h-1.612v-5.715L7 6"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="strikethrough-y">
+ <path id="strikethrough" d="M6 11h12v1H6v-1z"/>
+ <path id="a" d="M7 6h1.724l3.288 4.935L15.276 6H17l-4.194 6.285V18h-1.612v-5.715L7 6"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M4 9h12v2H4V9zm0 3h8v2H4v-2zm0-7h16v3H4V5zm16 14H4v-3h16v3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M4 9h12v2H4V9zm0 3h8v2H4v-2zm0-7h16v3H4V5zm16 14H4v-3h16v3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M20 9H8v2h12V9zm0 3h-8v2h8v-2zm0-7H4v3h16V5zM4 19h16v-3H4v3z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M20 9H8v2h12V9zm0 3h-8v2h8v-2zm0-7H4v3h16V5zM4 19h16v-3H4v3z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M20 19H4v-2h16v2zM20 15H4v-2h16v2zM20 11H4V9h16v2z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M20 19H4v-2h16v2zM20 15H4v-2h16v2zM20 11H4V9h16v2z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M20 11H4V9h16v2zM4 12h8v2H4v-2z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M20 11H4V9h16v2zM4 12h8v2H4v-2z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M4 11h16V9H4v2zm16 1h-8v2h8v-2z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M4 11h16V9H4v2zm16 1h-8v2h8v-2z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M17 13H4v-3h13v3zm-5 6H4v-3h8v3zM4 7V4h16v3H4z"/>
</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: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
<path d="M17 13H4v-3h13v3zm-5 6H4v-3h8v3zM4 7V4h16v3H4z"/>
</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M7 13h13v-3H7v3zm5 6h8v-3h-8v3zm8-12V4H4v3h16z"/>
</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: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
<path d="M7 13h13v-3H7v3zm5 6h8v-3h-8v3zm8-12V4H4v3h16z"/>
</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path id="x" d="M14 9l-2.354 3.406L14 16h-1.2L11 13.25 9.2 16H8l2.403-3.662L8 9h1.188l1.857 2.494L12.797 9H14z"/>
<path d="M18 13l-1 1v3l1 1h-1l-.527-.46L16 18h-1l1-1v-3l-1-1h1l.485.497L17 13z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path id="x" d="M14 9l-2.354 3.406L14 16h-1.2L11 13.25 9.2 16H8l2.403-3.662L8 9h1.188l1.857 2.494L12.797 9H14z"/>
+ <path d="M18 13l-1 1v3l1 1h-1l-.527-.46L16 18h-1l1-1v-3l-1-1h1l.485.497L17 13z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path id="x" d="M12 9l2.354 3.406L12 16h1.2l1.8-2.75L16.8 16H18l-2.403-3.662L18 9h-1.188l-1.857 2.494L13.203 9H12z"/>
<path d="M8 13l1 1v3l-1 1h1l.527-.46L10 18h1l-1-1v-3l1-1h-1l-.485.497L9 13z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path id="x" d="M12 9l2.354 3.406L12 16h1.2l1.8-2.75L16.8 16H18l-2.403-3.662L18 9h-1.188l-1.857 2.494L13.203 9H12z"/>
+ <path d="M8 13l1 1v3l-1 1h1l.527-.46L10 18h1l-1-1v-3l1-1h-1l-.485.497L9 13z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M18.1 5.1c0 .3-.1.6-.3.9l-1.4 1.4-.9-.8 2.2-2.2c.3.1.4.4.4.7zm-.5 5.3h3.2c0 .3-.1.6-.4.9s-.5.4-.8.4h-2v-1.3zm-6.2-5V2.2c.3 0 .6.1.9.4s.4.5.4.8v2h-1.3zm6.4 11.7c-.3 0-.6-.1-.8-.3l-1.4-1.4.8-.8 2.2 2.2c-.2.2-.5.3-.8.3zM6.2 4.9c.3 0 .6.1.8.3l1.4 1.4-.8.9-2.2-2.3c.2-.2.5-.3.8-.3zm5.2 11.7h1.2v3.2c-.3 0-.6-.1-.9-.4s-.4-.5-.4-.8l.1-2zm-7-6.2h2v1.2H3.2c0-.3.1-.6.4-.9s.5-.3.8-.3zM6.2 16l1.4-1.4.8.8-2.2 2.2c-.2-.2-.3-.5-.3-.8s.1-.6.3-.8z"/>
<circle cx="12" cy="11" r="4"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M18.1 5.1c0 .3-.1.6-.3.9l-1.4 1.4-.9-.8 2.2-2.2c.3.1.4.4.4.7zm-.5 5.3h3.2c0 .3-.1.6-.4.9s-.5.4-.8.4h-2v-1.3zm-6.2-5V2.2c.3 0 .6.1.9.4s.4.5.4.8v2h-1.3zm6.4 11.7c-.3 0-.6-.1-.8-.3l-1.4-1.4.8-.8 2.2 2.2c-.2.2-.5.3-.8.3zM6.2 4.9c.3 0 .6.1.8.3l1.4 1.4-.8.9-2.2-2.3c.2-.2.5-.3.8-.3zm5.2 11.7h1.2v3.2c-.3 0-.6-.1-.9-.4s-.4-.5-.4-.8l.1-2zm-7-6.2h2v1.2H3.2c0-.3.1-.6.4-.9s.5-.3.8-.3zM6.2 16l1.4-1.4.8.8-2.2 2.2c-.2-.2-.3-.5-.3-.8s.1-.6.3-.8z"/>
+ <circle cx="12" cy="11" r="4"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M5.9 5.1c0 .3.1.6.3.9l1.4 1.4.9-.8-2.2-2.2c-.3.1-.4.4-.4.7zm.5 5.3H3.2c0 .3.1.6.4.9.3.3.5.4.8.4h2v-1.3zm6.2-5V2.2c-.3 0-.6.1-.9.4-.3.3-.4.5-.4.8v2h1.3zM6.2 17.1c.3 0 .6-.1.8-.3l1.4-1.4-.8-.8-2.2 2.2c.2.2.5.3.8.3zM17.8 4.9c-.3 0-.6.1-.8.3l-1.4 1.4.8.9 2.2-2.3c-.2-.2-.5-.3-.8-.3zm-5.2 11.7h-1.2v3.2c.3 0 .6-.1.9-.4.3-.3.4-.5.4-.8l-.1-2zm7-6.2h-2v1.2h3.2c0-.3-.1-.6-.4-.9-.3-.3-.5-.3-.8-.3zM17.8 16l-1.4-1.4-.8.8 2.2 2.2c.2-.2.3-.5.3-.8 0-.3-.1-.6-.3-.8z"/>
<circle cx="12" cy="11" r="4" transform="matrix(-1 0 0 1 24 0)"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M5.9 5.1c0 .3.1.6.3.9l1.4 1.4.9-.8-2.2-2.2c-.3.1-.4.4-.4.7zm.5 5.3H3.2c0 .3.1.6.4.9.3.3.5.4.8.4h2v-1.3zm6.2-5V2.2c-.3 0-.6.1-.9.4-.3.3-.4.5-.4.8v2h1.3zM6.2 17.1c.3 0 .6-.1.8-.3l1.4-1.4-.8-.8-2.2 2.2c.2.2.5.3.8.3zM17.8 4.9c-.3 0-.6.1-.8.3l-1.4 1.4.8.9 2.2-2.3c-.2-.2-.5-.3-.8-.3zm-5.2 11.7h-1.2v3.2c.3 0 .6-.1.9-.4.3-.3.4-.5.4-.8l-.1-2zm7-6.2h-2v1.2h3.2c0-.3-.1-.6-.4-.9-.3-.3-.5-.3-.8-.3zM17.8 16l-1.4-1.4-.8.8 2.2 2.2c.2-.2.3-.5.3-.8 0-.3-.1-.6-.3-.8z"/>
+ <circle cx="12" cy="11" r="4" transform="matrix(-1 0 0 1 24 0)"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path id="x" d="M14 9l-2.354 3.406L14 16h-1.2L11 13.25 9.2 16H8l2.403-3.662L8 9h1.188l1.857 2.494L12.797 9H14z"/>
<path d="M18 7l-1 1v3l1 1h-1l-.527-.46L16 12h-1l1-1V8l-1-1h1l.485.497L17 7z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path id="x" d="M14 9l-2.354 3.406L14 16h-1.2L11 13.25 9.2 16H8l2.403-3.662L8 9h1.188l1.857 2.494L12.797 9H14z"/>
+ <path d="M18 7l-1 1v3l1 1h-1l-.527-.46L16 12h-1l1-1V8l-1-1h1l.485.497L17 7z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path id="x" d="M12 9l2.354 3.406L12 16h1.2l1.8-2.75L16.8 16H18l-2.403-3.662L18 9h-1.188l-1.857 2.494L13.203 9H12z"/>
<path d="M8 7l1 1v3l-1 1h1l.527-.46L10 12h1l-1-1V8l1-1h-1l-.485.497L9 7z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path id="x" d="M12 9l2.354 3.406L12 16h1.2l1.8-2.75L16.8 16H18l-2.403-3.662L18 9h-1.188l-1.857 2.494L13.203 9H12z"/>
+ <path d="M8 7l1 1v3l-1 1h1l.527-.46L10 12h1l-1-1V8l1-1h-1l-.485.497L9 7z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="table-caption">
<path id="caption" d="M6 6h12v3H6z"/>
<path id="table" d="M4 10v7h16v-7H4zm1 1h4v2H5v-2zm5 0h4v2h-4v-2zm5 0h4v2h-4v-2zM5 14h4v2H5v-2zm5 0h4v2h-4v-2zm5 0h4v2h-4v-2z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="table-caption">
+ <path id="caption" d="M6 6h12v3H6z"/>
+ <path id="table" d="M4 10v7h16v-7H4zm1 1h4v2H5v-2zm5 0h4v2h-4v-2zm5 0h4v2h-4v-2zM5 14h4v2H5v-2zm5 0h4v2h-4v-2zm5 0h4v2h-4v-2z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="table-insert-column-ltr">
<path d="M13 9h-2v2H9v2h2v2h2v-2h2v-2h-2z" id="plus"/>
<path d="M5 5h2v14H5z" id="column"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="table-insert-column-ltr">
+ <path d="M13 9h-2v2H9v2h2v2h2v-2h2v-2h-2z" id="plus"/>
+ <path d="M5 5h2v14H5z" id="column"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="table-insert-column-rtl">
<path d="M13 9h-2v2H9v2h2v2h2v-2h2v-2h-2z" id="plus"/>
<path d="M17 5h2v14h-2z" id="column"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="table-insert-column-rtl">
+ <path d="M13 9h-2v2H9v2h2v2h2v-2h2v-2h-2z" id="plus"/>
+ <path d="M17 5h2v14h-2z" id="column"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="table-insert-row-after">
<path d="M13 9h-2v2H9v2h2v2h2v-2h2v-2h-2z" id="plus"/>
<path d="M5 17h14v2H5z" id="row"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="table-insert-row-after">
+ <path d="M13 9h-2v2H9v2h2v2h2v-2h2v-2h-2z" id="plus"/>
+ <path d="M5 17h14v2H5z" id="row"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="table-insert-row-before">
<path d="M13 9h-2v2H9v2h2v2h2v-2h2v-2h-2z" id="plus"/>
<path d="M5 5h14v2H5z" id="row"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="table-insert-row-before">
+ <path d="M13 9h-2v2H9v2h2v2h2v-2h2v-2h-2z" id="plus"/>
+ <path d="M5 5h14v2H5z" id="row"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="table-insert">
<path id="table" d="M4 6v11h15V6zm1 3h6v3H5zm7 0h6v3h-6zm-7 4h6v3H5zm7 0h6v3h-6z"/>
</g>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="table-merge-cells">
<g id="merge-cell-left">
<path id="cell-border" d="M4 7v9h7v-3l-1 .834V15H5V8h5v1.167L11 10V7z"/>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
+ <g id="table-merge-cells">
+ <g id="merge-cell-left">
+ <path id="cell-border" d="M4 7v9h7v-3l-1 .834V15H5V8h5v1.167L11 10V7z"/>
+ <path id="arrow" d="M8 9v2H6v1h2v2l3-2.5z"/>
+ </g>
+ <use id="merge-cell-right" xlink:href="#merge-cell-left" transform="matrix(-1 0 0 1 24 0)"/>
+ </g>
+</svg>
--- /dev/null
+<?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: #36c }</style>
+ <g id="table-insert">
+ <path id="table" d="M4 6v11h15V6zm1 3h6v3H5zm7 0h6v3h-6zm-7 4h6v3H5zm7 0h6v3h-6z"/>
+ </g>
+</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: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</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: #d11d13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</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: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="template-add">
<path id="add" d="M23 7h-4V3h-2v4h-4v2h4v4h2V9h4z"/>
<path id="template" d="M18 14v4H6c-1.1 0-2-.9-2-2V8h8V7H3v9c0 1.7 1.3 3 3 3h13v-5z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="template-add">
+ <path id="add" d="M23 7h-4V3h-2v4h-4v2h4v4h2V9h4z"/>
+ <path id="template" d="M18 14v4H6c-1.1 0-2-.9-2-2V8h8V7H3v9c0 1.7 1.3 3 3 3h13v-5z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="template-add">
<path id="add" d="M1 7h4V3h2v4h4v2H7v4H5V9H1z"/>
<path id="template" d="M6 14v4h12c1.1 0 2-.9 2-2V8h-8V7h9v9c0 1.7-1.3 3-3 3H5v-5z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="template-add">
+ <path id="add" d="M1 7h4V3h2v4h4v2H7v4H5V9H1z"/>
+ <path id="template" d="M6 14v4h12c1.1 0 2-.9 2-2V8h-8V7h9v9c0 1.7-1.3 3-3 3H5v-5z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M7 7H5V6h2l.47.5L8 6h2v1H8v10h2v1H8l-.5-.53L7 18H5v-1h2zm6.976 9v-2H11v-4h2.976V8.044L20 12.022z" id="text-dir-ltr"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M7 7H5V6h2l.47.5L8 6h2v1H8v10h2v1H8l-.5-.53L7 18H5v-1h2zm6.976 9v-2H11v-4h2.976V8.044L20 12.022z" id="text-dir-ltr"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M17 17h2v1h-2l-.47-.5-.53.5h-2v-1h2V7h-2V6h2l.5.53L17 6h2v1h-2zm-6.976-9v2H13v4h-2.976v1.956L4 11.978z" id="text-dir-rtl"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M17 17h2v1h-2l-.47-.5-.53.5h-2v-1h2V7h-2V6h2l.5.53L17 6h2v1h-2zm-6.976-9v2H13v4h-2.976v1.956L4 11.978z" id="text-dir-rtl"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="text-style">
<path id="a" d="M15.296 18h2.79l-1.14-12h-2.79L6 18h2.79l2.038-3h4.183l.29 3zm-3.11-5L14.5 9.6l.323 3.4H12.19z"/>
<path id="underline" d="M6 19h12v1H6v-1z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="text-style">
+ <path id="a" d="M15.296 18h2.79l-1.14-12h-2.79L6 18h2.79l2.038-3h4.183l.29 3zm-3.11-5L14.5 9.6l.323 3.4H12.19z"/>
+ <path id="underline" d="M6 19h12v1H6v-1z"/>
+ </g>
+</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: #d11d13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M6 8c0-1.1.9-2 2-2h2l1-1h2l1 1h2c1.1 0 2 .9 2 2H6zm1 1h10l-1 11H8z"/>
</svg>
--- /dev/null
+<?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: #36c }</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M20.5 20.5L5 5 4 6l3 3 1 11h8l.2-1.8 3.3 3.3zM17 9h-6l5.5 5.5zm1-1c0-1.1-.9-2-2-2h-2l-1-1h-2l-1 1H8l2 2h8z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M20.5 20.5L5 5 4 6l3 3 1 11h8l.2-1.8 3.3 3.3zM17 9h-6l5.5 5.5zm1-1c0-1.1-.9-2-2-2h-2l-1-1h-2l-1 1H8l2 2h8z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M4 20.5L19.5 5l1 1-3 3-1 11h-8l-.2-1.8L5 21.5zM7.5 9h6L8 14.5zm-1-1c0-1.1.9-2 2-2h2l1-1h2l1 1h2l-2 2h-8z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M4 20.5L19.5 5l1 1-3 3-1 11h-8l-.2-1.8L5 21.5zM7.5 9h6L8 14.5zm-1-1c0-1.1.9-2 2-2h2l1-1h2l1 1h2l-2 2h-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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M3 13.35l1.8-7.2c.2-.996.81-1.8 1.8-1.8h10.8c.99 0 1.6.867 1.8 1.8l1.8 7.2v4.5c0 .99-.81 1.8-1.8 1.8H4.8c-.99 0-1.8-.81-1.8-1.8v-4.5zm6.96 1.8h4.08c-.49.557-1.212.9-2.04.9a2.68 2.68 0 0 1-2.04-.9h4.08c.414-.472.66-1.098.66-1.8h4.14l-1.44-7.2H6.6l-1.44 7.2H9.3c0 .702.246 1.328.66 1.8z" id="tray"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M3 13.35l1.8-7.2c.2-.996.81-1.8 1.8-1.8h10.8c.99 0 1.6.867 1.8 1.8l1.8 7.2v4.5c0 .99-.81 1.8-1.8 1.8H4.8c-.99 0-1.8-.81-1.8-1.8v-4.5zm6.96 1.8h4.08c-.49.557-1.212.9-2.04.9a2.68 2.68 0 0 1-2.04-.9h4.08c.414-.472.66-1.098.66-1.8h4.14l-1.44-7.2H6.6l-1.44 7.2H9.3c0 .702.246 1.328.66 1.8z" id="tray"/>
+</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: #d11d13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</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>
--- /dev/null
+<?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: #36c }</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: #d11d13 }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #c33 }</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>
<?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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</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>
--- /dev/null
+<?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: #36c }</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>
<?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: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
<path d="M21 11l-6-1-3-6-3 6-6 1 4 4-1 6 6-3 6 3-1-6 4-4z"/>
</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M21 11l-6-1-3-6-3 6-6 1 4 4-1 6 6-3 6 3-1-6 4-4z"/>
</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: #347bff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #36c }</style>
<path d="M21 11l-6-1-3-6-3 6-6 1 4 4-1 6 6-3 6 3-1-6 4-4z"/>
</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="underline-a">
<path id="a" d="M14.424 16H16.5L13.037 6H10.96L7.5 16h2.077l.627-2h3.604l.616 2zm-3.92-3.623L12 7.997l1.51 4.38h-3z"/>
<path id="underline" d="M7 17h10v1H7v-1z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="underline-a">
+ <path id="a" d="M14.424 16H16.5L13.037 6H10.96L7.5 16h2.077l.627-2h3.604l.616 2zm-3.92-3.623L12 7.997l1.51 4.38h-3z"/>
+ <path id="underline" d="M7 17h10v1H7v-1z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="underline-u">
<path id="u" d="M8 6h2v5.96c-.104 1.706.695 2 2 2.04 1.777.062 2.002-.88 2-2.04V6h2v6.123c0 1.28-.338 2.245-1.016 2.898-.672.658-1.666.98-2.98.98-1.32 0-2.32-.32-2.996-.98C8.336 14.37 8 13.41 8 12.13V6"/>
<path id="underline" d="M7 17h10v1H7v-1z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="underline-u">
+ <path id="u" d="M8 6h2v5.96c-.104 1.706.695 2 2 2.04 1.777.062 2.002-.88 2-2.04V6h2v6.123c0 1.28-.338 2.245-1.016 2.898-.672.658-1.666.98-2.98.98-1.32 0-2.32-.32-2.996-.98C8.336 14.37 8 13.41 8 12.13V6"/>
+ <path id="underline" d="M7 17h10v1H7v-1z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M12 8l8 10H4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M12 8l8 10H4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M10 13c0 1.7 1.3 3 3 3V9h3l-4.5-5L7 9h3v4zm7 0v5H7c-.6 0-1-.4-1-1v-4H4v4c0 1.9 1.3 3 3 3h12v-7h-2z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M10 13c0 1.7 1.3 3 3 3V9h3l-4.5-5L7 9h3v4zm7 0v5H7c-.6 0-1-.4-1-1v-4H4v4c0 1.9 1.3 3 3 3h12v-7h-2z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M13 13c0 1.7-1.3 3-3 3V9H7l4.5-5L16 9h-3v4zm-7 0v5h10c.6 0 1-.4 1-1v-4h2v4c0 1.9-1.3 3-3 3H4v-7h2z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M13 13c0 1.7-1.3 3-3 3V9H7l4.5-5L16 9h-3v4zm-7 0v5h10c.6 0 1-.4 1-1v-4h2v4c0 1.9-1.3 3-3 3H4v-7h2z"/>
+</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: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M16 5H4v12c0 1.7 1.3 3 3 3h12V8c0-1.7-1.3-3-3-3zm-2 4c.7 0 1.2.6 1.2 1.2s-.6 1.2-1.2 1.2-1.2-.6-1.2-1.2S13.3 9 14 9zM9 9c.7 0 1.2.6 1.2 1.2s-.5 1.3-1.2 1.3-1.2-.6-1.2-1.2S8.3 9 9 9zm7 5.4c0 .2-.1.3-.3.5-.7.6-1.6 1-2.6 1.3s-2.1.2-3.1 0-2-.9-2.7-1.5c-.1-.1-.2-.3-.2-.4s.1-.3.2-.4c.1-.1.3-.2.4-.2.2 0 .3.1.4.2.5.5 1.2.9 2.1 1.1s1.7.2 2.6 0 1.6-.5 2.1-1c.1-.1.3-.2.4-.2s.3.1.5.2.2.2.2.4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M16 5H4v12c0 1.7 1.3 3 3 3h12V8c0-1.7-1.3-3-3-3zm-2 4c.7 0 1.2.6 1.2 1.2s-.6 1.2-1.2 1.2-1.2-.6-1.2-1.2S13.3 9 14 9zM9 9c.7 0 1.2.6 1.2 1.2s-.5 1.3-1.2 1.3-1.2-.6-1.2-1.2S8.3 9 9 9zm7 5.4c0 .2-.1.3-.3.5-.7.6-1.6 1-2.6 1.3s-2.1.2-3.1 0-2-.9-2.7-1.5c-.1-.1-.2-.3-.2-.4s.1-.3.2-.4c.1-.1.3-.2.4-.2.2 0 .3.1.4.2.5.5 1.2.9 2.1 1.1s1.7.2 2.6 0 1.6-.5 2.1-1c.1-.1.3-.2.4-.2s.3.1.5.2.2.2.2.4z"/>
+</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: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M7 5h12v12c0 1.7-1.3 3-3 3H4V8c0-1.7 1.3-3 3-3zm2 4c-.7 0-1.2.6-1.2 1.2s.6 1.2 1.2 1.2 1.2-.6 1.2-1.2S9.7 9 9 9zm5 0c-.7 0-1.2.6-1.2 1.2s.5 1.3 1.2 1.3 1.2-.6 1.2-1.2S14.7 9 14 9zm-7 5.4c0 .2.1.3.3.5.7.6 1.6 1 2.6 1.3 1 .3 2.1.2 3.1 0s2-.9 2.7-1.5c.1-.1.2-.3.2-.4 0-.1-.1-.3-.2-.4-.1-.1-.3-.2-.4-.2-.2 0-.3.1-.4.2-.5.5-1.2.9-2.1 1.1-.9.2-1.7.2-2.6 0-.9-.2-1.6-.5-2.1-1-.1-.1-.3-.2-.4-.2-.1 0-.3.1-.5.2s-.2.2-.2.4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M7 5h12v12c0 1.7-1.3 3-3 3H4V8c0-1.7 1.3-3 3-3zm2 4c-.7 0-1.2.6-1.2 1.2s.6 1.2 1.2 1.2 1.2-.6 1.2-1.2S9.7 9 9 9zm5 0c-.7 0-1.2.6-1.2 1.2s.5 1.3 1.2 1.3 1.2-.6 1.2-1.2S14.7 9 14 9zm-7 5.4c0 .2.1.3.3.5.7.6 1.6 1 2.6 1.3 1 .3 2.1.2 3.1 0s2-.9 2.7-1.5c.1-.1.2-.3.2-.4 0-.1-.1-.3-.2-.4-.1-.1-.3-.2-.4-.2-.2 0-.3.1-.4.2-.5.5-1.2.9-2.1 1.1-.9.2-1.7.2-2.6 0-.9-.2-1.6-.5-2.1-1-.1-.1-.3-.2-.4-.2-.1 0-.3.1-.5.2s-.2.2-.2.4z"/>
+</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: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M18.75 17.4c-1.08-.36-3.6-1.35-3.6-1.35-.81-.27-.81-.99-.9-1.8v-.09c1.26-1.08 2.25-2.88 2.25-4.86 0-4.23-1.8-5.85-4.5-5.85-1.89 0-4.5 1.08-4.5 5.85 0 1.89.99 3.69 2.25 4.86v.09c0 .81-.09 1.53-.9 1.8 0 0-2.61.99-3.6 1.35-1.17.36-2.25.9-2.25 2.25v.9h18v-.9c0-1.08-.72-1.8-2.25-2.25z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M18.75 17.4c-1.08-.36-3.6-1.35-3.6-1.35-.81-.27-.81-.99-.9-1.8v-.09c1.26-1.08 2.25-2.88 2.25-4.86 0-4.23-1.8-5.85-4.5-5.85-1.89 0-4.5 1.08-4.5 5.85 0 1.89.99 3.69 2.25 4.86v.09c0 .81-.09 1.53-.9 1.8 0 0-2.61.99-3.6 1.35-1.17.36-2.25.9-2.25 2.25v.9h18v-.9c0-1.08-.72-1.8-2.25-2.25z"/>
+</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: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M16 5H4v12c0 1.7 1.3 3 3 3h12V8c0-1.7-1.3-3-3-3zm-9.3 5.4C6.2 10 6 9.6 6 9c.6.6 1.5.9 2.5.9s1.9-.3 2.5-.9c0 .6-.2 1-.7 1.4-.5.4-1.1.6-1.8.6s-1.3-.2-1.8-.6zm8.4 4.3c0 .2-.1.3-.3.4-1 .6-2.2.9-3.5.9-1.2 0-2.3-.3-3.3-1-.2-.1-.2-.2-.3-.4s0-.3.1-.5.2-.2.4-.3.3 0 .5.1c.8.5 1.7.8 2.8.8s2-.2 2.8-.7c.1-.1.3-.1.5-.1s.3.1.4.3l-.1.5zm1.2-4.3c-.5.4-1.1.6-1.8.6s-1.3-.2-1.8-.6S12 9.6 12 9c.6.6 1.5.9 2.5.9s1.9-.3 2.5-.9c0 .6-.2 1-.7 1.4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M16 5H4v12c0 1.7 1.3 3 3 3h12V8c0-1.7-1.3-3-3-3zm-9.3 5.4C6.2 10 6 9.6 6 9c.6.6 1.5.9 2.5.9s1.9-.3 2.5-.9c0 .6-.2 1-.7 1.4-.5.4-1.1.6-1.8.6s-1.3-.2-1.8-.6zm8.4 4.3c0 .2-.1.3-.3.4-1 .6-2.2.9-3.5.9-1.2 0-2.3-.3-3.3-1-.2-.1-.2-.2-.3-.4s0-.3.1-.5.2-.2.4-.3.3 0 .5.1c.8.5 1.7.8 2.8.8s2-.2 2.8-.7c.1-.1.3-.1.5-.1s.3.1.4.3l-.1.5zm1.2-4.3c-.5.4-1.1.6-1.8.6s-1.3-.2-1.8-.6S12 9.6 12 9c.6.6 1.5.9 2.5.9s1.9-.3 2.5-.9c0 .6-.2 1-.7 1.4z"/>
+</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: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M7 5h12v12c0 1.7-1.3 3-3 3H4V8c0-1.7 1.3-3 3-3zm9.3 5.4c.5-.4.7-.8.7-1.4-.6.6-1.5.9-2.5.9S12.6 9.6 12 9c0 .6.2 1 .7 1.4.5.4 1.1.6 1.8.6s1.3-.2 1.8-.6zm-8.4 4.3c0 .2.1.3.3.4 1 .6 2.2.9 3.5.9 1.2 0 2.3-.3 3.3-1 .2-.1.2-.2.3-.4.1-.2 0-.3-.1-.5s-.2-.2-.4-.3c-.2-.1-.3 0-.5.1-.8.5-1.7.8-2.8.8-1.1 0-2-.2-2.8-.7-.1-.1-.3-.1-.5-.1s-.3.1-.4.3l.1.5zm-1.2-4.3c.5.4 1.1.6 1.8.6s1.3-.2 1.8-.6c.5-.4.7-.8.7-1.4-.6.6-1.5.9-2.5.9S6.6 9.6 6 9c0 .6.2 1 .7 1.4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M7 5h12v12c0 1.7-1.3 3-3 3H4V8c0-1.7 1.3-3 3-3zm9.3 5.4c.5-.4.7-.8.7-1.4-.6.6-1.5.9-2.5.9S12.6 9.6 12 9c0 .6.2 1 .7 1.4.5.4 1.1.6 1.8.6s1.3-.2 1.8-.6zm-8.4 4.3c0 .2.1.3.3.4 1 .6 2.2.9 3.5.9 1.2 0 2.3-.3 3.3-1 .2-.1.2-.2.3-.4.1-.2 0-.3-.1-.5s-.2-.2-.4-.3c-.2-.1-.3 0-.5.1-.8.5-1.7.8-2.8.8-1.1 0-2-.2-2.8-.7-.1-.1-.3-.1-.5-.1s-.3.1-.4.3l.1.5zm-1.2-4.3c.5.4 1.1.6 1.8.6s1.3-.2 1.8-.6c.5-.4.7-.8.7-1.4-.6.6-1.5.9-2.5.9S6.6 9.6 6 9c0 .6.2 1 .7 1.4z"/>
+</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: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M5 6v11l-3 3h17c1.7 0 3-1.3 3-3V6H5zm11.2 2.5c.7 0 1.2.6 1.2 1.2s-.5 1.3-1.2 1.3-1.2-.6-1.2-1.2.6-1.3 1.2-1.3zm-5.4 0c.7 0 1.2.6 1.2 1.2s-.6 1.3-1.2 1.3-1.2-.6-1.2-1.2.5-1.3 1.2-1.3zm2.7 8.5c-5.1 0-6-5-6-5s2 1 6 1l6-1s-1 5-6 5z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M5 6v11l-3 3h17c1.7 0 3-1.3 3-3V6H5zm11.2 2.5c.7 0 1.2.6 1.2 1.2s-.5 1.3-1.2 1.3-1.2-.6-1.2-1.2.6-1.3 1.2-1.3zm-5.4 0c.7 0 1.2.6 1.2 1.2s-.6 1.3-1.2 1.3-1.2-.6-1.2-1.2.5-1.3 1.2-1.3zm2.7 8.5c-5.1 0-6-5-6-5s2 1 6 1l6-1s-1 5-6 5z"/>
+</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: #FFFFFF }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M19 6v11l3 3H5c-1.7 0-3-1.3-3-3V6h17zM7.8 8.5c-.7 0-1.2.6-1.2 1.2S7.1 11 7.8 11 9 10.4 9 9.8s-.6-1.3-1.2-1.3zm5.4 0c-.7 0-1.2.6-1.2 1.2s.6 1.3 1.2 1.3 1.2-.6 1.2-1.2-.5-1.3-1.2-1.3zM10.5 17c5.1 0 6-5 6-5s-2 1-6 1l-6-1s1 5 6 5z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M19 6v11l3 3H5c-1.7 0-3-1.3-3-3V6h17zM7.8 8.5c-.7 0-1.2.6-1.2 1.2S7.1 11 7.8 11 9 10.4 9 9.8s-.6-1.3-1.2-1.3zm5.4 0c-.7 0-1.2.6-1.2 1.2s.6 1.3 1.2 1.3 1.2-.6 1.2-1.2-.5-1.3-1.2-1.3zM10.5 17c5.1 0 6-5 6-5s-2 1-6 1l-6-1s1 5 6 5z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="viewCompact">
<circle cx="6" cy="6" r="2"/>
<circle cx="12" cy="6" r="2"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="viewCompact">
+ <circle cx="6" cy="6" r="2"/>
+ <circle cx="12" cy="6" r="2"/>
+ <circle cx="18" cy="6" r="2"/>
+ <circle cx="6" cy="12" r="2"/>
+ <circle cx="12" cy="12" r="2"/>
+ <circle cx="18" cy="12" r="2"/>
+ <circle cx="6" cy="18" r="2"/>
+ <circle cx="12" cy="18" r="2"/>
+ <circle cx="18" cy="18" r="2"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="viewDetails">
<circle cx="5.5" cy="8.5" r="2.5"/>
<path d="M10 6h12v1H10zm0 2h9v1h-9zm0 2h4v1h-4z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="viewDetails">
+ <circle cx="5.5" cy="8.5" r="2.5"/>
+ <path d="M10 6h12v1H10zm0 2h9v1h-9zm0 2h4v1h-4z"/>
+ <circle cx="5.5" cy="16.5" r="2.5"/>
+ <path d="M10 14h12v1H10zm0 2h9v1h-9zm0 2h4v1h-4z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="viewDetails">
<circle cx="18.5" cy="8.5" r="2.5"/>
<path d="M14 6H2v1h12zm0 2H5v1h9zm0 2h-4v1h4z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="viewDetails">
+ <circle cx="18.5" cy="8.5" r="2.5"/>
+ <path d="M14 6H2v1h12zm0 2H5v1h9zm0 2h-4v1h4z"/>
+ <circle cx="18.5" cy="16.5" r="2.5"/>
+ <path d="M14 14H2v1h12zm0 2H5v1h9zm0 2h-4v1h4z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M13 14h5v1h-5v-1zm0 3h5v-1h-5v1zm0 1h5v1h-5v-1zm-1-5v3l-5 3 1-6-4-3 6-1 2-5s1.9 5 2 5l6 1-4 3h-4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M13 14h5v1h-5v-1zm0 3h5v-1h-5v1zm0 1h5v1h-5v-1zm-1-5v3l-5 3 1-6-4-3 6-1 2-5s1.9 5 2 5l6 1-4 3h-4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M11 14H6v1h5v-1zm0 3H6v-1h5v1zm0 1H6v1h5v-1zm1-5v3l5 3-1-6 4-3-6-1-2-5s-1.9 5-2 5l-6 1 4 3h4z"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M11 14H6v1h5v-1zm0 3H6v-1h5v1zm0 1H6v1h5v-1zm1-5v3l5 3-1-6 4-3-6-1-2-5s-1.9 5-2 5l-6 1 4 3h4z"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="wikiText">
<path id="opening-bracket-inner" d="M7 19h3v-2H9V7h1V5H7z"/>
<path id="closing-bracket-inner" d="M17 19h-3v-2h1V7h-1V5h3z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="wikiText">
+ <path id="opening-bracket-inner" d="M7 19h3v-2H9V7h1V5H7z"/>
+ <path id="closing-bracket-inner" d="M17 19h-3v-2h1V7h-1V5h3z"/>
+ <path id="closing-bracket-outer" d="M21 19h-3v-2h1V7h-1V5h3z"/>
+ <path id="opening-bracket-outer" d="M3 19h3v-2H5V7h1V5H3z"/>
+ </g>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M15 9l.7-1.8c.9.4 1.8.7 2.4.9l-.6 1.7v.2L15 9zm-4.3-1.9l.8-1.8c1.2.5 2.6 1.1 3 1.4l-.8 1.8-3-1.4zm-5.9-1c-.8 0-1.4.2-2 .6L1.7 5c.9-.6 1.9-.9 3.1-.9v2zm-4.3.7l1.8.8c-.3.7-.3 1.3-.1 1.8l-1.9.7C0 8.9 0 7.8.5 6.8zm4.2 5.4l-1.3 1.5c-1-1-1.7-1.6-2-2l1.5-1.3c.7.8 1.3 1.4 1.8 1.8zm7.3 4.3c0 1.9-1.6 3.5-3.5 3.5S5 18.4 5 16.5 6.6 13 8.5 13s3.5 1.6 3.5 3.5zM24 8l-1-1-1.5 1.5L20 7l-1 1 1.5 1.5L19 11l1 1 1.5-1.5L23 12l1-1-1.5-1.5z"/>
<circle cx="8" cy="5" r="2"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M15 9l.7-1.8c.9.4 1.8.7 2.4.9l-.6 1.7v.2L15 9zm-4.3-1.9l.8-1.8c1.2.5 2.6 1.1 3 1.4l-.8 1.8-3-1.4zm-5.9-1c-.8 0-1.4.2-2 .6L1.7 5c.9-.6 1.9-.9 3.1-.9v2zm-4.3.7l1.8.8c-.3.7-.3 1.3-.1 1.8l-1.9.7C0 8.9 0 7.8.5 6.8zm4.2 5.4l-1.3 1.5c-1-1-1.7-1.6-2-2l1.5-1.3c.7.8 1.3 1.4 1.8 1.8zm7.3 4.3c0 1.9-1.6 3.5-3.5 3.5S5 18.4 5 16.5 6.6 13 8.5 13s3.5 1.6 3.5 3.5zM24 8l-1-1-1.5 1.5L20 7l-1 1 1.5 1.5L19 11l1 1 1.5-1.5L23 12l1-1-1.5-1.5z"/>
+ <circle cx="8" cy="5" r="2"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<path d="M9.095 9l-.7-1.8c-.9.4-1.8.7-2.4.9l.6 1.7v.2l2.5-1zm4.3-1.9l-.8-1.8c-1.2.5-2.6 1.1-3 1.4l.8 1.8 3-1.4zm5.9-1c.8 0 1.4.2 2 .6l1.1-1.7c-.9-.6-1.9-.9-3.1-.9v2zm4.3.7l-1.8.8c.3.7.3 1.3.1 1.8l1.9.7c.3-1.2.3-2.3-.2-3.3zm-4.2 5.4l1.3 1.5c1-1 1.7-1.6 2-2l-1.5-1.3c-.7.8-1.3 1.4-1.8 1.8zm-7.3 4.3c0 1.9 1.6 3.5 3.5 3.5s3.5-1.6 3.5-3.5-1.6-3.5-3.5-3.5-3.5 1.6-3.5 3.5zM.095 8l1-1 1.5 1.5 1.5-1.5 1 1-1.5 1.5 1.5 1.5-1 1-1.5-1.5-1.5 1.5-1-1 1.5-1.5z"/>
<circle cx="8" cy="5" r="2" transform="matrix(-1 0 0 1 24.095 0)"/>
</svg>
--- /dev/null
+<?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: #36c }</style>
+ <path d="M9.095 9l-.7-1.8c-.9.4-1.8.7-2.4.9l.6 1.7v.2l2.5-1zm4.3-1.9l-.8-1.8c-1.2.5-2.6 1.1-3 1.4l.8 1.8 3-1.4zm5.9-1c.8 0 1.4.2 2 .6l1.1-1.7c-.9-.6-1.9-.9-3.1-.9v2zm4.3.7l-1.8.8c.3.7.3 1.3.1 1.8l1.9.7c.3-1.2.3-2.3-.2-3.3zm-4.2 5.4l1.3 1.5c1-1 1.7-1.6 2-2l-1.5-1.3c-.7.8-1.3 1.4-1.8 1.8zm-7.3 4.3c0 1.9 1.6 3.5 3.5 3.5s3.5-1.6 3.5-3.5-1.6-3.5-3.5-3.5-3.5 1.6-3.5 3.5zM.095 8l1-1 1.5 1.5 1.5-1.5 1 1-1.5 1.5 1.5 1.5-1 1-1.5-1.5-1.5 1.5-1-1 1.5-1.5z"/>
+ <circle cx="8" cy="5" r="2" transform="matrix(-1 0 0 1 24.095 0)"/>
+</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: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><style>* { fill: #fff }</style>
<g id="window">
<path id="title" d="M7 10h10v1H7z"/>
<path id="frame" d="M16 19H8c-2.206 0-4-1.794-4-4V9c0-2.206 1.794-4 4-4h8c2.206 0 4 1.794 4 4v6c0 2.206-1.794 4-4 4zM8 7c-1.103 0-2 .897-2 2v6c0 1.103.897 2 2 2h8c1.103 0 2-.897 2-2V9c0-1.103-.897-2-2-2H8z"/>
--- /dev/null
+<?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: #36c }</style>
+ <g id="window">
+ <path id="title" d="M7 10h10v1H7z"/>
+ <path id="frame" d="M16 19H8c-2.206 0-4-1.794-4-4V9c0-2.206 1.794-4 4-4h8c2.206 0 4 1.794 4 4v6c0 2.206-1.794 4-4 4zM8 7c-1.103 0-2 .897-2 2v6c0 1.103.897 2 2 2h8c1.103 0 2-.897 2-2V9c0-1.103-.897-2-2-2H8z"/>
+ </g>
+</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #fff }</style>
<path d="M6 12A6 6 0 1 1 6 0a6 6 0 0 1 0 12zM5 7h2V2H5zm0 3h2V8H5z" id="alert"/>
</svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #36c }</style>
+ <path d="M6 12A6 6 0 1 1 6 0a6 6 0 0 1 0 12zM5 7h2V2H5zm0 3h2V8H5z" id="alert"/>
+</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #fff }</style>
<g id="down">
<path id="arrow" d="M1 4h10L6 9 1 4"/>
</g>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #36c }</style>
+ <g id="down">
+ <path id="arrow" d="M1 4h10L6 9 1 4"/>
+ </g>
+</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #fff }</style>
<g id="ltr">
<path id="arrow" d="M4 1v10l5-5-5-5"/>
</g>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #36c }</style>
+ <g id="ltr">
+ <path id="arrow" d="M4 1v10l5-5-5-5"/>
+ </g>
+</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #fff }</style>
<g id="rtl">
<path id="arrow" d="M8 11V1L3 6l5 5"/>
</g>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #36c }</style>
+ <g id="rtl">
+ <path id="arrow" d="M8 11V1L3 6l5 5"/>
+ </g>
+</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #fff }</style>
<g id="up">
<path id="arrow" d="M1 8h10L6 3 1 8"/>
</g>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #36c }</style>
+ <g id="up">
+ <path id="arrow" d="M1 8h10L6 3 1 8"/>
+ </g>
+</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #fff }</style>
<g id="clear">
<path id="circle-with-cross" d="M6 0C2.7 0 0 2.7 0 6s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6zM3.5 2.5L6 5l2.5-2.5 1 1L7 6l2.5 2.5-1 1L6 7 3.5 9.5l-1-1L5 6 2.5 3.5z"/>
</g>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #36c }</style>
+ <g id="clear">
+ <path id="circle-with-cross" d="M6 0C2.7 0 0 2.7 0 6s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6zM3.5 2.5L6 5l2.5-2.5 1 1L7 6l2.5 2.5-1 1L6 7 3.5 9.5l-1-1L5 6 2.5 3.5z"/>
+ </g>
+</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #fff }</style>
<path d="M5 1h2v10H5zm4.83 1.634l1 1.732-8.66 5-1-1.732zM1.17 4.366l1-1.732 8.66 5-1 1.732z" id="required"/>
</svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #36c }</style>
+ <path d="M5 1h2v10H5zm4.83 1.634l1 1.732-8.66 5-1-1.732zM1.17 4.366l1-1.732 8.66 5-1 1.732z" id="required"/>
+</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #fff }</style>
<g id="search">
<path id="magnifying-glass" d="M10.37 9.474L7.994 7.1l-.17-.1a3.45 3.45 0 0 0 .644-2.01A3.478 3.478 0 1 0 4.99 8.47c.75 0 1.442-.24 2.01-.648l.098.17 2.375 2.373c.19.188.543.142.79-.105s.293-.6.104-.79zm-5.38-2.27a2.21 2.21 0 1 1 2.21-2.21A2.21 2.21 0 0 1 4.99 7.21z"/>
</g>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #36c }</style>
+ <g id="search">
+ <path id="magnifying-glass" d="M10.37 9.474L7.994 7.1l-.17-.1a3.45 3.45 0 0 0 .644-2.01A3.478 3.478 0 1 0 4.99 8.47c.75 0 1.442-.24 2.01-.648l.098.17 2.375 2.373c.19.188.543.142.79-.105s.293-.6.104-.79zm-5.38-2.27a2.21 2.21 0 1 1 2.21-2.21A2.21 2.21 0 0 1 4.99 7.21z"/>
+ </g>
+</svg>
<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #ffffff }</style>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #fff }</style>
<g id="search">
<path id="magnifying-glass" d="M1.63 9.474L4.006 7.1l.17-.1a3.45 3.45 0 0 1-.644-2.01A3.478 3.478 0 1 1 7.01 8.47 3.43 3.43 0 0 1 5 7.822l-.098.17-2.375 2.373c-.19.188-.543.142-.79-.105s-.293-.6-.104-.79zm5.378-2.27A2.21 2.21 0 1 0 4.8 4.994 2.21 2.21 0 0 0 7.01 7.21z"/>
</g>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><style>* { fill: #36c }</style>
+ <g id="search">
+ <path id="magnifying-glass" d="M1.63 9.474L4.006 7.1l.17-.1a3.45 3.45 0 0 1-.644-2.01A3.478 3.478 0 1 1 7.01 8.47 3.43 3.43 0 0 1 5 7.822l-.098.17-2.375 2.373c-.19.188-.543.142-.79-.105s-.293-.6-.104-.79zm5.378-2.27A2.21 2.21 0 1 0 4.8 4.994 2.21 2.21 0 0 0 7.01 7.21z"/>
+ </g>
+</svg>
"intro": "@import '../../../../src/styles/common';",
"variants": {
"invert": {
- "color": "#ffffff",
+ "color": "#fff",
"global": true
+ },
+ "progressive": {
+ "color": "#36c",
+ "global": true
+ },
+ "constructive": {
+ "color": "#36c"
+ },
+ "destructive": {
+ "color": "#c33"
+ },
+ "warning": {
+ "color": "#ff5d00"
}
},
"images": {
+++ /dev/null
-Copyright (c) 2013 Kevin van Zonneveld (http://kvz.io)
-and Contributors (http://phpjs.org/authors)
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
/*!
* JavaScript for Special:MovePage
*/
-jQuery( function () {
+jQuery( function ( $ ) {
// Infuse for pretty dropdown
OO.ui.infuse( 'wpNewTitle' );
// Limit to 255 bytes, not characters
OO.ui.infuse( 'wpReason' ).$input.byteLimit();
// Infuse for nicer "help" popup
- OO.ui.infuse( 'wpMovetalk-field' );
+ if ( $( '#wpMovetalk-field' ).length ) {
+ OO.ui.infuse( 'wpMovetalk-field' );
+ }
} );
* @constructor
* @param {string|mw.Uri} url URL pointing to another wiki's `api.php` endpoint.
* @param {Object} [options] See mw.Api.
+ * @param {Object} [options.anonymous=false] Perform all requests anonymously. Use this option if
+ * the target wiki may otherwise not accept cross-origin requests, or if you don't need to
+ * perform write actions or read restricted information and want to avoid the overhead.
*
* @author Bartosz Dziewoński
* @author Jon Robson
}
this.apiUrl = String( url );
+ this.anonymous = options && options.anonymous;
options = $.extend( /*deep=*/ true,
{
ajax: {
url: this.apiUrl,
xhrFields: {
- withCredentials: true
+ withCredentials: this.anonymous ? false : true
}
},
parameters: {
* @return {string}
*/
CoreForeignApi.prototype.getOrigin = function () {
- var origin = location.protocol + '//' + location.hostname;
+ var origin;
+ if ( this.anonymous ) {
+ return '*';
+ }
+ origin = location.protocol + '//' + location.hostname;
if ( location.port ) {
origin += ':' + location.port;
}
return this.upload.getApi()
.then( function ( api ) {
// 'amenableparser' will expand templates and parser functions server-side.
- // We still do the rest of wikitext parsing here (throught jqueryMsg).
+ // We still do the rest of wikitext parsing here (through jqueryMsg).
return api.loadMessagesIfMissing( [ error.message.key ], { amenableparser: true } )
.then( function () {
if ( !mw.message( error.message.key ).exists() ) {
+++ /dev/null
-<?php
-/**
- * AutoLoader for the testing suite.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Testing
- */
-
-global $wgAutoloadClasses;
-$testDir = __DIR__;
-
-$wgAutoloadClasses += [
-
- # tests
- 'DbTestPreviewer' => "$testDir/testHelpers.inc",
- 'DbTestRecorder' => "$testDir/testHelpers.inc",
- 'DelayedParserTest' => "$testDir/testHelpers.inc",
- 'ParserTestResult' => "$testDir/parser/ParserTestResult.php",
- 'TestFileIterator' => "$testDir/testHelpers.inc",
- 'TestFileDataProvider' => "$testDir/testHelpers.inc",
- 'TestRecorder' => "$testDir/testHelpers.inc",
- 'ITestRecorder' => "$testDir/testHelpers.inc",
- 'DjVuSupport' => "$testDir/testHelpers.inc",
- 'TidySupport' => "$testDir/testHelpers.inc",
-
- # tests/phpunit
- 'MediaWikiTestCase' => "$testDir/phpunit/MediaWikiTestCase.php",
- 'MediaWikiPHPUnitTestListener' => "$testDir/phpunit/MediaWikiPHPUnitTestListener.php",
- 'MediaWikiLangTestCase' => "$testDir/phpunit/MediaWikiLangTestCase.php",
- 'ResourceLoaderTestCase' => "$testDir/phpunit/ResourceLoaderTestCase.php",
- 'ResourceLoaderTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
- 'ResourceLoaderFileModuleTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
- 'EmptyResourceLoader' => "$testDir/phpunit/ResourceLoaderTestCase.php",
- 'TestUser' => "$testDir/phpunit/includes/TestUser.php",
- 'TestUserRegistry' => "$testDir/phpunit/includes/TestUserRegistry.php",
- 'LessFileCompilationTest' => "$testDir/phpunit/LessFileCompilationTest.php",
-
- # tests/phpunit/includes
- 'RevisionStorageTest' => "$testDir/phpunit/includes/RevisionStorageTest.php",
- 'TestingAccessWrapper' => "$testDir/phpunit/includes/TestingAccessWrapper.php",
- 'TestLogger' => "$testDir/phpunit/includes/TestLogger.php",
-
- # tests/phpunit/includes/api
- 'ApiFormatTestBase' => "$testDir/phpunit/includes/api/format/ApiFormatTestBase.php",
- 'ApiQueryTestBase' => "$testDir/phpunit/includes/api/query/ApiQueryTestBase.php",
- 'ApiQueryContinueTestBase' => "$testDir/phpunit/includes/api/query/ApiQueryContinueTestBase.php",
- 'ApiTestCase' => "$testDir/phpunit/includes/api/ApiTestCase.php",
- 'ApiTestCaseUpload' => "$testDir/phpunit/includes/api/ApiTestCaseUpload.php",
- 'ApiTestContext' => "$testDir/phpunit/includes/api/ApiTestContext.php",
- 'MockApi' => "$testDir/phpunit/includes/api/MockApi.php",
- 'MockApiQueryBase' => "$testDir/phpunit/includes/api/MockApiQueryBase.php",
- 'UserWrapper' => "$testDir/phpunit/includes/api/UserWrapper.php",
- 'RandomImageGenerator' => "$testDir/phpunit/includes/api/RandomImageGenerator.php",
-
- # tests/phpunit/includes/auth
- 'MediaWiki\\Auth\\AuthenticationRequestTestCase' =>
- "$testDir/phpunit/includes/auth/AuthenticationRequestTestCase.php",
-
- # tests/phpunit/includes/changes
- 'TestRecentChangesHelper' => "$testDir/phpunit/includes/changes/TestRecentChangesHelper.php",
-
- # tests/phpunit/includes/content
- 'DummyContentHandlerForTesting' =>
- "$testDir/phpunit/mocks/content/DummyContentHandlerForTesting.php",
- 'DummyContentForTesting' => "$testDir/phpunit/mocks/content/DummyContentForTesting.php",
- 'DummyNonTextContentHandler' => "$testDir/phpunit/mocks/content/DummyNonTextContentHandler.php",
- 'DummyNonTextContent' => "$testDir/phpunit/mocks/content/DummyNonTextContent.php",
- 'ContentHandlerTest' => "$testDir/phpunit/includes/content/ContentHandlerTest.php",
- 'JavaScriptContentTest' => "$testDir/phpunit/includes/content/JavaScriptContentTest.php",
- 'TextContentTest' => "$testDir/phpunit/includes/content/TextContentTest.php",
- 'WikitextContentTest' => "$testDir/phpunit/includes/content/WikitextContentTest.php",
-
- # tests/phpunit/includes/db
- 'DatabaseTestHelper' => "$testDir/phpunit/includes/db/DatabaseTestHelper.php",
-
- # tests/phpunit/includes/diff
- 'FakeDiffOp' => "$testDir/phpunit/includes/diff/FakeDiffOp.php",
-
- # tests/phpunit/includes/logging
- 'LogFormatterTestCase' => "$testDir/phpunit/includes/logging/LogFormatterTestCase.php",
-
- # tests/phpunit/includes/page
- 'WikiPageTest' => "$testDir/phpunit/includes/page/WikiPageTest.php",
-
- # tests/phpunit/includes/password
- 'PasswordTestCase' => "$testDir/phpunit/includes/password/PasswordTestCase.php",
-
- # tests/phpunit/includes/resourceloader
- 'ResourceLoaderImageModuleTest' =>
- "$testDir/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php",
- 'ResourceLoaderImageModuleTestable' =>
- "$testDir/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php",
-
- # tests/phpunit/includes/session
- 'MediaWiki\\Session\\TestBagOStuff' => "$testDir/phpunit/includes/session/TestBagOStuff.php",
- 'MediaWiki\\Session\\TestUtils' => "$testDir/phpunit/includes/session/TestUtils.php",
-
- # tests/phpunit/includes/specials
- 'SpecialPageTestBase' => "$testDir/phpunit/includes/specials/SpecialPageTestBase.php",
- 'SpecialPageExecutor' => "$testDir/phpunit/includes/specials/SpecialPageExecutor.php",
-
- # tests/phpunit/languages
- 'LanguageClassesTestCase' => "$testDir/phpunit/languages/LanguageClassesTestCase.php",
-
- # tests/phpunit/includes/libs
- 'GenericArrayObjectTest' => "$testDir/phpunit/includes/libs/GenericArrayObjectTest.php",
-
- # tests/phpunit/maintenance
- 'DumpTestCase' => "$testDir/phpunit/maintenance/DumpTestCase.php",
-
- # tests/phpunit/media
- 'FakeDimensionFile' => "$testDir/phpunit/includes/media/FakeDimensionFile.php",
- 'MediaWikiMediaTestCase' => "$testDir/phpunit/includes/media/MediaWikiMediaTestCase.php",
-
- # tests/phpunit/mocks
- 'MockFSFile' => "$testDir/phpunit/mocks/filebackend/MockFSFile.php",
- 'MockFileBackend' => "$testDir/phpunit/mocks/filebackend/MockFileBackend.php",
- 'MockBitmapHandler' => "$testDir/phpunit/mocks/media/MockBitmapHandler.php",
- 'MockImageHandler' => "$testDir/phpunit/mocks/media/MockImageHandler.php",
- 'MockSvgHandler' => "$testDir/phpunit/mocks/media/MockSvgHandler.php",
- 'MockDjVuHandler' => "$testDir/phpunit/mocks/media/MockDjVuHandler.php",
- 'MockOggHandler' => "$testDir/phpunit/mocks/media/MockOggHandler.php",
- 'MockMediaHandlerFactory' => "$testDir/phpunit/mocks/media/MockMediaHandlerFactory.php",
- 'MockWebRequest' => "$testDir/phpunit/mocks/MockWebRequest.php",
- 'MediaWiki\\Session\\DummySessionBackend'
- => "$testDir/phpunit/mocks/session/DummySessionBackend.php",
- 'DummySessionProvider' => "$testDir/phpunit/mocks/session/DummySessionProvider.php",
-
- # tests/parser
- 'NewParserTest' => "$testDir/phpunit/includes/parser/NewParserTest.php",
- 'MediaWikiParserTest' => "$testDir/phpunit/includes/parser/MediaWikiParserTest.php",
- 'ParserTest' => "$testDir/parser/parserTest.inc",
- 'ParserTestResultNormalizer' => "$testDir/parser/parserTest.inc",
- 'ParserTestParserHook' => "$testDir/parser/parserTestsParserHook.php",
-
- # tests/phpunit/includes/site
- 'SiteTest' => "$testDir/phpunit/includes/site/SiteTest.php",
- 'TestSites' => "$testDir/phpunit/includes/site/TestSites.php",
-
- # tests/phpunit/includes/specialpage
- 'SpecialPageTestHelper' => "$testDir/phpunit/includes/specialpage/SpecialPageTestHelper.php",
-];
--- /dev/null
+<?php
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Common code for test environment initialisation and teardown
+ */
+class TestSetup {
+ /**
+ * This should be called before Setup.php, e.g. from the finalSetup() method
+ * of a Maintenance subclass
+ */
+ public static function applyInitialConfig() {
+ global $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType, $wgMainWANCache;
+ global $wgMainStash;
+ global $wgLanguageConverterCacheType, $wgUseDatabaseMessages;
+ global $wgLocaltimezone, $wgLocalisationCacheConf;
+ global $wgDevelopmentWarnings;
+ global $wgSessionProviders, $wgSessionPbkdf2Iterations;
+ global $wgJobTypeConf;
+ global $wgAuthManagerConfig, $wgAuth;
+
+ // wfWarn should cause tests to fail
+ $wgDevelopmentWarnings = true;
+
+ // Make sure all caches and stashes are either disabled or use
+ // in-process cache only to prevent tests from using any preconfigured
+ // cache meant for the local wiki from outside the test run.
+ // See also MediaWikiTestCase::run() which mocks CACHE_DB and APC.
+
+ // Disabled in DefaultSettings, override local settings
+ $wgMainWANCache =
+ $wgMainCacheType = CACHE_NONE;
+ // Uses CACHE_ANYTHING in DefaultSettings, use hash instead of db
+ $wgMessageCacheType =
+ $wgParserCacheType =
+ $wgSessionCacheType =
+ $wgLanguageConverterCacheType = 'hash';
+ // Uses db-replicated in DefaultSettings
+ $wgMainStash = 'hash';
+ // Use memory job queue
+ $wgJobTypeConf = [
+ 'default' => [ 'class' => 'JobQueueMemory', 'order' => 'fifo' ],
+ ];
+
+ $wgUseDatabaseMessages = false; # Set for future resets
+
+ // Assume UTC for testing purposes
+ $wgLocaltimezone = 'UTC';
+
+ $wgLocalisationCacheConf['storeClass'] = 'LCStoreNull';
+
+ // Generic MediaWiki\Session\SessionManager configuration for tests
+ // We use CookieSessionProvider because things might be expecting
+ // cookies to show up in a FauxRequest somewhere.
+ $wgSessionProviders = [
+ [
+ 'class' => MediaWiki\Session\CookieSessionProvider::class,
+ 'args' => [ [
+ 'priority' => 30,
+ 'callUserSetCookiesHook' => true,
+ ] ],
+ ],
+ ];
+
+ // Single-iteration PBKDF2 session secret derivation, for speed.
+ $wgSessionPbkdf2Iterations = 1;
+
+ // Generic AuthManager configuration for testing
+ $wgAuthManagerConfig = [
+ 'preauth' => [],
+ 'primaryauth' => [
+ [
+ 'class' => MediaWiki\Auth\TemporaryPasswordPrimaryAuthenticationProvider::class,
+ 'args' => [ [
+ 'authoritative' => false,
+ ] ],
+ ],
+ [
+ 'class' => MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider::class,
+ 'args' => [ [
+ 'authoritative' => true,
+ ] ],
+ ],
+ ],
+ 'secondaryauth' => [],
+ ];
+ $wgAuth = new MediaWiki\Auth\AuthManagerAuthPlugin();
+
+ // Bug 44192 Do not attempt to send a real e-mail
+ Hooks::clear( 'AlternateUserMailer' );
+ Hooks::register(
+ 'AlternateUserMailer',
+ function () {
+ return false;
+ }
+ );
+ // xdebug's default of 100 is too low for MediaWiki
+ ini_set( 'xdebug.max_nesting_level', 1000 );
+
+ // Bug T116683 serialize_precision of 100
+ // may break testing against floating point values
+ // treated with PHP's serialize()
+ ini_set( 'serialize_precision', 17 );
+
+ // TODO: we should call MediaWikiTestCase::prepareServices( new GlobalVarConfig() ) here.
+ // But PHPUnit may not be loaded yet, so we have to wait until just
+ // before PHPUnit_TextUI_Command::main() is executed.
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * AutoLoader for the testing suite.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Testing
+ */
+
+global $wgAutoloadClasses;
+$testDir = __DIR__ . "/..";
+
+$wgAutoloadClasses += [
+
+ # tests/common
+ 'TestSetup' => "$testDir/common/TestSetup.php",
+
+ # tests/parser
+ 'DbTestPreviewer' => "$testDir/parser/DbTestPreviewer.php",
+ 'DbTestRecorder' => "$testDir/parser/DbTestRecorder.php",
+ 'DjVuSupport' => "$testDir/parser/DjVuSupport.php",
+ 'TestRecorder' => "$testDir/parser/TestRecorder.php",
+ 'MultiTestRecorder' => "$testDir/parser/MultiTestRecorder.php",
+ 'ParserTestRunner' => "$testDir/parser/ParserTestRunner.php",
+ 'ParserTestParserHook' => "$testDir/parser/ParserTestParserHook.php",
+ 'ParserTestPrinter' => "$testDir/parser/ParserTestPrinter.php",
+ 'ParserTestResult' => "$testDir/parser/ParserTestResult.php",
+ 'ParserTestResultNormalizer' => "$testDir/parser/ParserTestResultNormalizer.php",
+ 'PhpunitTestRecorder' => "$testDir/parser/PhpunitTestRecorder.php",
+ 'TestFileReader' => "$testDir/parser/TestFileReader.php",
+ 'TestRecorder' => "$testDir/parser/TestRecorder.php",
+ 'TidySupport' => "$testDir/parser/TidySupport.php",
+
+ # tests/phpunit
+ 'MediaWikiTestCase' => "$testDir/phpunit/MediaWikiTestCase.php",
+ 'MediaWikiPHPUnitTestListener' => "$testDir/phpunit/MediaWikiPHPUnitTestListener.php",
+ 'MediaWikiLangTestCase' => "$testDir/phpunit/MediaWikiLangTestCase.php",
+ 'ResourceLoaderTestCase' => "$testDir/phpunit/ResourceLoaderTestCase.php",
+ 'ResourceLoaderTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
+ 'ResourceLoaderFileModuleTestModule' => "$testDir/phpunit/ResourceLoaderTestCase.php",
+ 'EmptyResourceLoader' => "$testDir/phpunit/ResourceLoaderTestCase.php",
+ 'TestUser' => "$testDir/phpunit/includes/TestUser.php",
+ 'TestUserRegistry' => "$testDir/phpunit/includes/TestUserRegistry.php",
+ 'LessFileCompilationTest' => "$testDir/phpunit/LessFileCompilationTest.php",
+
+ # tests/phpunit/includes
+ 'RevisionStorageTest' => "$testDir/phpunit/includes/RevisionStorageTest.php",
+ 'TestingAccessWrapper' => "$testDir/phpunit/includes/TestingAccessWrapper.php",
+ 'TestLogger' => "$testDir/phpunit/includes/TestLogger.php",
+
+ # tests/phpunit/includes/api
+ 'ApiFormatTestBase' => "$testDir/phpunit/includes/api/format/ApiFormatTestBase.php",
+ 'ApiQueryTestBase' => "$testDir/phpunit/includes/api/query/ApiQueryTestBase.php",
+ 'ApiQueryContinueTestBase' => "$testDir/phpunit/includes/api/query/ApiQueryContinueTestBase.php",
+ 'ApiTestCase' => "$testDir/phpunit/includes/api/ApiTestCase.php",
+ 'ApiTestCaseUpload' => "$testDir/phpunit/includes/api/ApiTestCaseUpload.php",
+ 'ApiTestContext' => "$testDir/phpunit/includes/api/ApiTestContext.php",
+ 'MockApi' => "$testDir/phpunit/includes/api/MockApi.php",
+ 'MockApiQueryBase' => "$testDir/phpunit/includes/api/MockApiQueryBase.php",
+ 'UserWrapper' => "$testDir/phpunit/includes/api/UserWrapper.php",
+ 'RandomImageGenerator' => "$testDir/phpunit/includes/api/RandomImageGenerator.php",
+
+ # tests/phpunit/includes/auth
+ 'MediaWiki\\Auth\\AuthenticationRequestTestCase' =>
+ "$testDir/phpunit/includes/auth/AuthenticationRequestTestCase.php",
+
+ # tests/phpunit/includes/changes
+ 'TestRecentChangesHelper' => "$testDir/phpunit/includes/changes/TestRecentChangesHelper.php",
+
+ # tests/phpunit/includes/content
+ 'DummyContentHandlerForTesting' =>
+ "$testDir/phpunit/mocks/content/DummyContentHandlerForTesting.php",
+ 'DummyContentForTesting' => "$testDir/phpunit/mocks/content/DummyContentForTesting.php",
+ 'DummyNonTextContentHandler' => "$testDir/phpunit/mocks/content/DummyNonTextContentHandler.php",
+ 'DummyNonTextContent' => "$testDir/phpunit/mocks/content/DummyNonTextContent.php",
+ 'ContentHandlerTest' => "$testDir/phpunit/includes/content/ContentHandlerTest.php",
+ 'JavaScriptContentTest' => "$testDir/phpunit/includes/content/JavaScriptContentTest.php",
+ 'TextContentTest' => "$testDir/phpunit/includes/content/TextContentTest.php",
+ 'WikitextContentTest' => "$testDir/phpunit/includes/content/WikitextContentTest.php",
+
+ # tests/phpunit/includes/db
+ 'DatabaseTestHelper' => "$testDir/phpunit/includes/db/DatabaseTestHelper.php",
+
+ # tests/phpunit/includes/diff
+ 'FakeDiffOp' => "$testDir/phpunit/includes/diff/FakeDiffOp.php",
+
+ # tests/phpunit/includes/logging
+ 'LogFormatterTestCase' => "$testDir/phpunit/includes/logging/LogFormatterTestCase.php",
+
+ # tests/phpunit/includes/page
+ 'WikiPageTest' => "$testDir/phpunit/includes/page/WikiPageTest.php",
+
+ # tests/phpunit/includes/parser
+ 'ParserIntegrationTest' => "$testDir/phpunit/includes/parser/ParserIntegrationTest.php",
+
+ # tests/phpunit/includes/password
+ 'PasswordTestCase' => "$testDir/phpunit/includes/password/PasswordTestCase.php",
+
+ # tests/phpunit/includes/resourceloader
+ 'ResourceLoaderImageModuleTest' =>
+ "$testDir/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php",
+ 'ResourceLoaderImageModuleTestable' =>
+ "$testDir/phpunit/includes/resourceloader/ResourceLoaderImageModuleTest.php",
+
+ # tests/phpunit/includes/session
+ 'MediaWiki\\Session\\TestBagOStuff' => "$testDir/phpunit/includes/session/TestBagOStuff.php",
+ 'MediaWiki\\Session\\TestUtils' => "$testDir/phpunit/includes/session/TestUtils.php",
+
+ # tests/phpunit/includes/site
+ 'SiteTest' => "$testDir/phpunit/includes/site/SiteTest.php",
+ 'TestSites' => "$testDir/phpunit/includes/site/TestSites.php",
+
+ # tests/phpunit/includes/specialpage
+ 'SpecialPageTestHelper' => "$testDir/phpunit/includes/specialpage/SpecialPageTestHelper.php",
+
+ # tests/phpunit/includes/specials
+ 'SpecialPageTestBase' => "$testDir/phpunit/includes/specials/SpecialPageTestBase.php",
+ 'SpecialPageExecutor' => "$testDir/phpunit/includes/specials/SpecialPageExecutor.php",
+
+ # tests/phpunit/languages
+ 'LanguageClassesTestCase' => "$testDir/phpunit/languages/LanguageClassesTestCase.php",
+
+ # tests/phpunit/includes/libs
+ 'GenericArrayObjectTest' => "$testDir/phpunit/includes/libs/GenericArrayObjectTest.php",
+
+ # tests/phpunit/maintenance
+ 'DumpTestCase' => "$testDir/phpunit/maintenance/DumpTestCase.php",
+
+ # tests/phpunit/media
+ 'FakeDimensionFile' => "$testDir/phpunit/includes/media/FakeDimensionFile.php",
+ 'MediaWikiMediaTestCase' => "$testDir/phpunit/includes/media/MediaWikiMediaTestCase.php",
+
+ # tests/phpunit/mocks
+ 'MockFSFile' => "$testDir/phpunit/mocks/filebackend/MockFSFile.php",
+ 'MockFileBackend' => "$testDir/phpunit/mocks/filebackend/MockFileBackend.php",
+ 'MockBitmapHandler' => "$testDir/phpunit/mocks/media/MockBitmapHandler.php",
+ 'MockImageHandler' => "$testDir/phpunit/mocks/media/MockImageHandler.php",
+ 'MockSvgHandler' => "$testDir/phpunit/mocks/media/MockSvgHandler.php",
+ 'MockDjVuHandler' => "$testDir/phpunit/mocks/media/MockDjVuHandler.php",
+ 'MockOggHandler' => "$testDir/phpunit/mocks/media/MockOggHandler.php",
+ 'MockMediaHandlerFactory' => "$testDir/phpunit/mocks/media/MockMediaHandlerFactory.php",
+ 'MockWebRequest' => "$testDir/phpunit/mocks/MockWebRequest.php",
+ 'MediaWiki\\Session\\DummySessionBackend'
+ => "$testDir/phpunit/mocks/session/DummySessionBackend.php",
+ 'DummySessionProvider' => "$testDir/phpunit/mocks/session/DummySessionProvider.php",
+
+ # tests/suites
+ 'ParserTestFileSuite' => "$testDir/phpunit/suites/ParserTestFileSuite.php",
+ 'ParserTestTopLevelSuite' => "$testDir/phpunit/suites/ParserTestTopLevelSuite.php",
+];
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Testing
+ */
+
+class DbTestPreviewer extends TestRecorder {
+ protected $filter; // /< Test name filter callback
+ protected $lb; // /< Database load balancer
+ protected $db; // /< Database connection to the main DB
+ protected $curRun; // /< run ID number for the current run
+ protected $prevRun; // /< run ID number for the previous run, if any
+ protected $results; // /< Result array
+
+ /**
+ * This should be called before the table prefix is changed
+ */
+ function __construct( $db, $filter = false ) {
+ $this->db = $db;
+ $this->filter = $filter;
+ }
+
+ /**
+ * Set up result recording; insert a record for the run with the date
+ * and all that fun stuff
+ */
+ function start() {
+ if ( !$this->db->tableExists( 'testrun', __METHOD__ )
+ || !$this->db->tableExists( 'testitem', __METHOD__ )
+ ) {
+ print "WARNING> `testrun` table not found in database.\n";
+ $this->prevRun = false;
+ } else {
+ // We'll make comparisons against the previous run later...
+ $this->prevRun = $this->db->selectField( 'testrun', 'MAX(tr_id)' );
+ }
+
+ $this->results = [];
+ }
+
+ function record( $test, ParserTestResult $result ) {
+ $this->results[$test['desc']] = $result->isSuccess() ? 1 : 0;
+ }
+
+ function report() {
+ if ( $this->prevRun ) {
+ // f = fail, p = pass, n = nonexistent
+ // codes show before then after
+ $table = [
+ 'fp' => 'previously failing test(s) now PASSING! :)',
+ 'pn' => 'previously PASSING test(s) removed o_O',
+ 'np' => 'new PASSING test(s) :)',
+
+ 'pf' => 'previously passing test(s) now FAILING! :(',
+ 'fn' => 'previously FAILING test(s) removed O_o',
+ 'nf' => 'new FAILING test(s) :(',
+ 'ff' => 'still FAILING test(s) :(',
+ ];
+
+ $prevResults = [];
+
+ $res = $this->db->select( 'testitem', [ 'ti_name', 'ti_success' ],
+ [ 'ti_run' => $this->prevRun ], __METHOD__ );
+ $filter = $this->filter;
+
+ foreach ( $res as $row ) {
+ if ( !$filter || $filter( $row->ti_name ) ) {
+ $prevResults[$row->ti_name] = $row->ti_success;
+ }
+ }
+
+ $combined = array_keys( $this->results + $prevResults );
+
+ # Determine breakdown by change type
+ $breakdown = [];
+ foreach ( $combined as $test ) {
+ if ( !isset( $prevResults[$test] ) ) {
+ $before = 'n';
+ } elseif ( $prevResults[$test] == 1 ) {
+ $before = 'p';
+ } else /* if ( $prevResults[$test] == 0 )*/ {
+ $before = 'f';
+ }
+
+ if ( !isset( $this->results[$test] ) ) {
+ $after = 'n';
+ } elseif ( $this->results[$test] == 1 ) {
+ $after = 'p';
+ } else /*if ( $this->results[$test] == 0 ) */ {
+ $after = 'f';
+ }
+
+ $code = $before . $after;
+
+ if ( isset( $table[$code] ) ) {
+ $breakdown[$code][$test] = $this->getTestStatusInfo( $test, $after );
+ }
+ }
+
+ # Write out results
+ foreach ( $table as $code => $label ) {
+ if ( !empty( $breakdown[$code] ) ) {
+ $count = count( $breakdown[$code] );
+ printf( "\n%4d %s\n", $count, $label );
+
+ foreach ( $breakdown[$code] as $differing_test_name => $statusInfo ) {
+ print " * $differing_test_name [$statusInfo]\n";
+ }
+ }
+ }
+ } else {
+ print "No previous test runs to compare against.\n";
+ }
+
+ print "\n";
+ }
+
+ /**
+ * Returns a string giving information about when a test last had a status change.
+ * Could help to track down when regressions were introduced, as distinct from tests
+ * which have never passed (which are more change requests than regressions).
+ * @param string $testname
+ * @param string $after
+ * @return string
+ */
+ private function getTestStatusInfo( $testname, $after ) {
+ // If we're looking at a test that has just been removed, then say when it first appeared.
+ if ( $after == 'n' ) {
+ $changedRun = $this->db->selectField( 'testitem',
+ 'MIN(ti_run)',
+ [ 'ti_name' => $testname ],
+ __METHOD__ );
+ $appear = $this->db->selectRow( 'testrun',
+ [ 'tr_date', 'tr_mw_version' ],
+ [ 'tr_id' => $changedRun ],
+ __METHOD__ );
+
+ return "First recorded appearance: "
+ . date( "d-M-Y H:i:s", strtotime( $appear->tr_date ) )
+ . ", " . $appear->tr_mw_version;
+ }
+
+ // Otherwise, this test has previous recorded results.
+ // See when this test last had a different result to what we're seeing now.
+ $conds = [
+ 'ti_name' => $testname,
+ 'ti_success' => ( $after == 'f' ? "1" : "0" ) ];
+
+ if ( $this->curRun ) {
+ $conds[] = "ti_run != " . $this->db->addQuotes( $this->curRun );
+ }
+
+ $changedRun = $this->db->selectField( 'testitem', 'MAX(ti_run)', $conds, __METHOD__ );
+
+ // If no record of ever having had a different result.
+ if ( is_null( $changedRun ) ) {
+ if ( $after == "f" ) {
+ return "Has never passed";
+ } else {
+ return "Has never failed";
+ }
+ }
+
+ // Otherwise, we're looking at a test whose status has changed.
+ // (i.e. it used to work, but now doesn't; or used to fail, but is now fixed.)
+ // In this situation, give as much info as we can as to when it changed status.
+ $pre = $this->db->selectRow( 'testrun',
+ [ 'tr_date', 'tr_mw_version' ],
+ [ 'tr_id' => $changedRun ],
+ __METHOD__ );
+ $post = $this->db->selectRow( 'testrun',
+ [ 'tr_date', 'tr_mw_version' ],
+ [ "tr_id > " . $this->db->addQuotes( $changedRun ) ],
+ __METHOD__,
+ [ "LIMIT" => 1, "ORDER BY" => 'tr_id' ]
+ );
+
+ if ( $post ) {
+ $postDate = date( "d-M-Y H:i:s", strtotime( $post->tr_date ) ) . ", {$post->tr_mw_version}";
+ } else {
+ $postDate = 'now';
+ }
+
+ return ( $after == "f" ? "Introduced" : "Fixed" ) . " between "
+ . date( "d-M-Y H:i:s", strtotime( $pre->tr_date ) ) . ", " . $pre->tr_mw_version
+ . " and $postDate";
+ }
+}
+
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Testing
+ */
+
+class DbTestRecorder extends TestRecorder {
+ public $version;
+ private $db;
+
+ public function __construct( IDatabase $db ) {
+ $this->db = $db;
+ }
+
+ /**
+ * Set up result recording; insert a record for the run with the date
+ * and all that fun stuff
+ */
+ function start() {
+ $this->db->begin( __METHOD__ );
+
+ if ( !$this->db->tableExists( 'testrun' )
+ || !$this->db->tableExists( 'testitem' )
+ ) {
+ print "WARNING> `testrun` table not found in database. Trying to create table.\n";
+ $this->db->sourceFile( $this->db->patchPath( 'patch-testrun.sql' ) );
+ echo "OK, resuming.\n";
+ }
+
+ $this->db->insert( 'testrun',
+ [
+ 'tr_date' => $this->db->timestamp(),
+ 'tr_mw_version' => $this->version,
+ 'tr_php_version' => PHP_VERSION,
+ 'tr_db_version' => $this->db->getServerVersion(),
+ 'tr_uname' => php_uname()
+ ],
+ __METHOD__ );
+ if ( $this->db->getType() === 'postgres' ) {
+ $this->curRun = $this->db->currentSequenceValue( 'testrun_id_seq' );
+ } else {
+ $this->curRun = $this->db->insertId();
+ }
+ }
+
+ /**
+ * Record an individual test item's success or failure to the db
+ *
+ * @param array $test
+ * @param ParserTestResult $result
+ */
+ function record( $test, ParserTestResult $result ) {
+ $this->db->insert( 'testitem',
+ [
+ 'ti_run' => $this->curRun,
+ 'ti_name' => $test['desc'],
+ 'ti_success' => $result->isSuccess() ? 1 : 0,
+ ],
+ __METHOD__ );
+ }
+
+ /**
+ * Commit transaction and clean up for result recording
+ */
+ function end() {
+ $this->db->commit( __METHOD__ );
+ }
+}
+
--- /dev/null
+<?php
+
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Testing
+ */
+
+/**
+ * Initialize and detect the DjVu files support
+ */
+class DjVuSupport {
+
+ /**
+ * Initialises DjVu tools global with default values
+ */
+ public function __construct() {
+ global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML, $wgFileExtensions, $wgDjvuTxt;
+
+ $wgDjvuRenderer = $wgDjvuRenderer ? $wgDjvuRenderer : '/usr/bin/ddjvu';
+ $wgDjvuDump = $wgDjvuDump ? $wgDjvuDump : '/usr/bin/djvudump';
+ $wgDjvuToXML = $wgDjvuToXML ? $wgDjvuToXML : '/usr/bin/djvutoxml';
+ $wgDjvuTxt = $wgDjvuTxt ? $wgDjvuTxt : '/usr/bin/djvutxt';
+
+ if ( !in_array( 'djvu', $wgFileExtensions ) ) {
+ $wgFileExtensions[] = 'djvu';
+ }
+ }
+
+ /**
+ * Returns true if the DjVu tools are usable
+ *
+ * @return bool
+ */
+ public function isEnabled() {
+ global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML, $wgDjvuTxt;
+
+ return is_executable( $wgDjvuRenderer )
+ && is_executable( $wgDjvuDump )
+ && is_executable( $wgDjvuToXML )
+ && is_executable( $wgDjvuTxt );
+ }
+}
+
--- /dev/null
+<?php
+
+/**
+ * This is a TestRecorder representing a collection of other TestRecorders.
+ * It proxies calls to all constituent objects.
+ */
+class MultiTestRecorder extends TestRecorder {
+ private $recorders = [];
+
+ public function addRecorder( TestRecorder $recorder ) {
+ $this->recorders[] = $recorder;
+ }
+
+ private function proxy( $funcName, $args ) {
+ foreach ( $this->recorders as $recorder ) {
+ call_user_func_array( [ $recorder, $funcName ], $args );
+ }
+ }
+
+ public function start() {
+ $this->proxy( __FUNCTION__, func_get_args() );
+ }
+
+ public function startTest( $test ) {
+ $this->proxy( __FUNCTION__, func_get_args() );
+ }
+
+ public function startSuite( $path ) {
+ $this->proxy( __FUNCTION__, func_get_args() );
+ }
+
+ public function endSuite( $path ) {
+ $this->proxy( __FUNCTION__, func_get_args() );
+ }
+
+ public function record( $test, ParserTestResult $result ) {
+ $this->proxy( __FUNCTION__, func_get_args() );
+ }
+
+ public function warning( $message ) {
+ $this->proxy( __FUNCTION__, func_get_args() );
+ }
+
+ public function skipped( $test, $subtest ) {
+ $this->proxy( __FUNCTION__, func_get_args() );
+ }
+
+ public function report() {
+ $this->proxy( __FUNCTION__, func_get_args() );
+ }
+
+ public function end() {
+ $this->proxy( __FUNCTION__, func_get_args() );
+ }
+}
--- /dev/null
+<?php
+/**
+ * A basic extension that's used by the parser tests to test whether input and
+ * arguments are passed to extensions properly.
+ *
+ * Copyright © 2005, 2006 Ævar Arnfjörð Bjarmason
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Testing
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ */
+
+class ParserTestParserHook {
+
+ static function setup( &$parser ) {
+ $parser->setHook( 'tag', [ __CLASS__, 'dumpHook' ] );
+ $parser->setHook( 'tåg', [ __CLASS__, 'dumpHook' ] );
+ $parser->setHook( 'statictag', [ __CLASS__, 'staticTagHook' ] );
+ return true;
+ }
+
+ static function dumpHook( $in, $argv ) {
+ return "<pre>\n" .
+ var_export( $in, true ) . "\n" .
+ var_export( $argv, true ) . "\n" .
+ "</pre>";
+ }
+
+ static function staticTagHook( $in, $argv, $parser ) {
+ if ( !count( $argv ) ) {
+ $parser->static_tag_buf = $in;
+ return '';
+ } elseif ( count( $argv ) === 1 && isset( $argv['action'] )
+ && $argv['action'] === 'flush' && $in === null
+ ) {
+ // Clear the buffer, we probably don't need to
+ if ( isset( $parser->static_tag_buf ) ) {
+ $tmp = $parser->static_tag_buf;
+ } else {
+ $tmp = '';
+ }
+ $parser->static_tag_buf = null;
+ return $tmp;
+ } else { // wtf?
+ return
+ "\nCall this extension as <statictag>string</statictag> or as" .
+ " <statictag action=flush/>, not in any other way.\n" .
+ "text: " . var_export( $in, true ) . "\n" .
+ "argv: " . var_export( $argv, true ) . "\n";
+ }
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Testing
+ */
+
+/**
+ * This is a TestRecorder responsible for printing information about progress,
+ * success and failure to the console. It is specific to the parserTests.php
+ * frontend.
+ */
+class ParserTestPrinter extends TestRecorder {
+ private $total;
+ private $success;
+ private $skipped;
+ private $term;
+ private $showDiffs;
+ private $showProgress;
+ private $showFailure;
+ private $showOutput;
+ private $useDwdiff;
+ private $markWhitespace;
+ private $xmlError;
+
+ function __construct( $term, $options ) {
+ $this->term = $term;
+ $options += [
+ 'showDiffs' => true,
+ 'showProgress' => true,
+ 'showFailure' => true,
+ 'showOutput' => false,
+ 'useDwdiff' => false,
+ 'markWhitespace' => false,
+ ];
+ $this->showDiffs = $options['showDiffs'];
+ $this->showProgress = $options['showProgress'];
+ $this->showFailure = $options['showFailure'];
+ $this->showOutput = $options['showOutput'];
+ $this->useDwdiff = $options['useDwdiff'];
+ $this->markWhitespace = $options['markWhitespace'];
+ }
+
+ public function start() {
+ $this->total = 0;
+ $this->success = 0;
+ $this->skipped = 0;
+ }
+
+ public function startTest( $test ) {
+ if ( $this->showProgress ) {
+ $this->showTesting( $test['desc'] );
+ }
+ }
+
+ private function showTesting( $desc ) {
+ print "Running test $desc... ";
+ }
+
+ /**
+ * Show "Reading tests from ..."
+ *
+ * @param string $path
+ */
+ public function startSuite( $path ) {
+ print $this->term->color( 1 ) .
+ "Running parser tests from \"$path\"..." .
+ $this->term->reset() .
+ "\n";
+ }
+
+ public function endSuite( $path ) {
+ print "\n";
+ }
+
+ public function record( $test, ParserTestResult $result ) {
+ $this->total++;
+ $this->success += ( $result->isSuccess() ? 1 : 0 );
+
+ if ( $result->isSuccess() ) {
+ $this->showSuccess( $result );
+ } else {
+ $this->showFailure( $result );
+ }
+ }
+
+ /**
+ * Print a happy success message.
+ *
+ * @param ParserTestResult $testResult
+ * @return bool
+ */
+ private function showSuccess( ParserTestResult $testResult ) {
+ if ( $this->showProgress ) {
+ print $this->term->color( '1;32' ) . 'PASSED' . $this->term->reset() . "\n";
+ }
+ }
+
+ /**
+ * Print a failure message and provide some explanatory output
+ * about what went wrong if so configured.
+ *
+ * @param ParserTestResult $testResult
+ * @return bool
+ */
+ private function showFailure( ParserTestResult $testResult ) {
+ if ( $this->showFailure ) {
+ if ( !$this->showProgress ) {
+ # In quiet mode we didn't show the 'Testing' message before the
+ # test, in case it succeeded. Show it now:
+ $this->showTesting( $testResult->getDescription() );
+ }
+
+ print $this->term->color( '31' ) . 'FAILED!' . $this->term->reset() . "\n";
+
+ if ( $this->showOutput ) {
+ print "--- Expected ---\n{$testResult->expected}\n";
+ print "--- Actual ---\n{$testResult->actual}\n";
+ }
+
+ if ( $this->showDiffs ) {
+ print $this->quickDiff( $testResult->expected, $testResult->actual );
+ if ( !$this->wellFormed( $testResult->actual ) ) {
+ print "XML error: $this->xmlError\n";
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Run given strings through a diff and return the (colorized) output.
+ * Requires writable /tmp directory and a 'diff' command in the PATH.
+ *
+ * @param string $input
+ * @param string $output
+ * @param string $inFileTail Tailing for the input file name
+ * @param string $outFileTail Tailing for the output file name
+ * @return string
+ */
+ private function quickDiff( $input, $output,
+ $inFileTail = 'expected', $outFileTail = 'actual'
+ ) {
+ if ( $this->markWhitespace ) {
+ $pairs = [
+ "\n" => '¶',
+ ' ' => '·',
+ "\t" => '→'
+ ];
+ $input = strtr( $input, $pairs );
+ $output = strtr( $output, $pairs );
+ }
+
+ # Windows, or at least the fc utility, is retarded
+ $slash = wfIsWindows() ? '\\' : '/';
+ $prefix = wfTempDir() . "{$slash}mwParser-" . mt_rand();
+
+ $infile = "$prefix-$inFileTail";
+ $this->dumpToFile( $input, $infile );
+
+ $outfile = "$prefix-$outFileTail";
+ $this->dumpToFile( $output, $outfile );
+
+ $shellInfile = wfEscapeShellArg( $infile );
+ $shellOutfile = wfEscapeShellArg( $outfile );
+
+ global $wgDiff3;
+ // we assume that people with diff3 also have usual diff
+ if ( $this->useDwdiff ) {
+ $shellCommand = 'dwdiff -Pc';
+ } else {
+ $shellCommand = ( wfIsWindows() && !$wgDiff3 ) ? 'fc' : 'diff -au';
+ }
+
+ $diff = wfShellExec( "$shellCommand $shellInfile $shellOutfile" );
+
+ unlink( $infile );
+ unlink( $outfile );
+
+ if ( $this->useDwdiff ) {
+ return $diff;
+ } else {
+ return $this->colorDiff( $diff );
+ }
+ }
+
+ /**
+ * Write the given string to a file, adding a final newline.
+ *
+ * @param string $data
+ * @param string $filename
+ */
+ private function dumpToFile( $data, $filename ) {
+ $file = fopen( $filename, "wt" );
+ fwrite( $file, $data . "\n" );
+ fclose( $file );
+ }
+
+ /**
+ * Colorize unified diff output if set for ANSI color output.
+ * Subtractions are colored blue, additions red.
+ *
+ * @param string $text
+ * @return string
+ */
+ private function colorDiff( $text ) {
+ return preg_replace(
+ [ '/^(-.*)$/m', '/^(\+.*)$/m' ],
+ [ $this->term->color( 34 ) . '$1' . $this->term->reset(),
+ $this->term->color( 31 ) . '$1' . $this->term->reset() ],
+ $text );
+ }
+
+ private function wellFormed( $text ) {
+ $html =
+ Sanitizer::hackDocType() .
+ '<html>' .
+ $text .
+ '</html>';
+
+ $parser = xml_parser_create( "UTF-8" );
+
+ # case folding violates XML standard, turn it off
+ xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
+
+ if ( !xml_parse( $parser, $html, true ) ) {
+ $err = xml_error_string( xml_get_error_code( $parser ) );
+ $position = xml_get_current_byte_index( $parser );
+ $fragment = $this->extractFragment( $html, $position );
+ $this->xmlError = "$err at byte $position:\n$fragment";
+ xml_parser_free( $parser );
+
+ return false;
+ }
+
+ xml_parser_free( $parser );
+
+ return true;
+ }
+
+ private function extractFragment( $text, $position ) {
+ $start = max( 0, $position - 10 );
+ $before = $position - $start;
+ $fragment = '...' .
+ $this->term->color( 34 ) .
+ substr( $text, $start, $before ) .
+ $this->term->color( 0 ) .
+ $this->term->color( 31 ) .
+ $this->term->color( 1 ) .
+ substr( $text, $position, 1 ) .
+ $this->term->color( 0 ) .
+ $this->term->color( 34 ) .
+ substr( $text, $position + 1, 9 ) .
+ $this->term->color( 0 ) .
+ '...';
+ $display = str_replace( "\n", ' ', $fragment );
+ $caret = ' ' .
+ str_repeat( ' ', $before ) .
+ $this->term->color( 31 ) .
+ '^' .
+ $this->term->color( 0 );
+
+ return "$display\n$caret";
+ }
+
+ /**
+ * Show a warning to the user
+ */
+ public function warning( $message ) {
+ echo "$message\n";
+ }
+
+ /**
+ * Mark a test skipped
+ */
+ public function skipped( $test, $subtest ) {
+ if ( $this->showProgress ) {
+ print $this->term->color( '1;33' ) . 'SKIPPED' . $this->term->reset() . "\n";
+ }
+ $this->skipped++;
+ }
+
+ public function report() {
+ if ( $this->total > 0 ) {
+ $this->reportPercentage( $this->success, $this->total );
+ } else {
+ print $this->term->color( 31 ) . "No tests found." . $this->term->reset() . "\n";
+ }
+ }
+
+ private function reportPercentage( $success, $total ) {
+ $ratio = wfPercent( 100 * $success / $total );
+ print $this->term->color( 1 ) . "Passed $success of $total tests ($ratio)";
+ if ( $this->skipped ) {
+ print ", skipped {$this->skipped}";
+ }
+ print "... ";
+
+ if ( $success == $total ) {
+ print $this->term->color( 32 ) . "ALL TESTS PASSED!";
+ } else {
+ $failed = $total - $success;
+ print $this->term->color( 31 ) . "$failed tests failed!";
+ }
+
+ print $this->term->reset() . "\n";
+
+ return ( $success == $total );
+ }
+}
+
* @since 1.22
*/
class ParserTestResult {
- /**
- * Description of the parser test.
- *
- * This is usually the text used to describe a parser test in the .txt
- * files. It is initialized on a construction and you most probably
- * never want to change it.
- */
- public $description;
+ /** The test info array */
+ public $test;
/** Text that was expected */
public $expected;
/** Actual text rendered */
public $actual;
/**
- * @param string $description A short text describing the parser test
- * usually the text in the parser test .txt file. The description
- * is later available using the property $description.
+ * @param array $test The test info array from TestIterator
+ * @param string $expected The normalized expected output
+ * @param string $actual The actual output
*/
- public function __construct( $description ) {
- $this->description = $description;
+ public function __construct( $test, $expected, $actual ) {
+ $this->test = $test;
+ $this->expected = $expected;
+ $this->actual = $actual;
}
/**
public function isSuccess() {
return $this->expected === $this->actual;
}
+
+ public function getDescription() {
+ return $this->test['desc'];
+ }
}
--- /dev/null
+<?php
+/**
+ * @file
+ * @ingroup Testing
+ */
+
+class ParserTestResultNormalizer {
+ protected $doc, $xpath, $invalid;
+
+ public static function normalize( $text, $funcs ) {
+ $norm = new self( $text );
+ if ( $norm->invalid ) {
+ return $text;
+ }
+ foreach ( $funcs as $func ) {
+ $norm->$func();
+ }
+ return $norm->serialize();
+ }
+
+ protected function __construct( $text ) {
+ $this->doc = new DOMDocument( '1.0', 'utf-8' );
+
+ // Note: parsing a supposedly XHTML document with an XML parser is not
+ // guaranteed to give accurate results. For example, it may introduce
+ // differences in the number of line breaks in <pre> tags.
+
+ MediaWiki\suppressWarnings();
+ if ( !$this->doc->loadXML( '<html><body>' . $text . '</body></html>' ) ) {
+ $this->invalid = true;
+ }
+ MediaWiki\restoreWarnings();
+ $this->xpath = new DOMXPath( $this->doc );
+ $this->body = $this->xpath->query( '//body' )->item( 0 );
+ }
+
+ protected function removeTbody() {
+ foreach ( $this->xpath->query( '//tbody' ) as $tbody ) {
+ while ( $tbody->firstChild ) {
+ $child = $tbody->firstChild;
+ $tbody->removeChild( $child );
+ $tbody->parentNode->insertBefore( $child, $tbody );
+ }
+ $tbody->parentNode->removeChild( $tbody );
+ }
+ }
+
+ /**
+ * The point of this function is to produce a normalized DOM in which
+ * Tidy's output matches the output of html5depurate. Tidy both trims
+ * and pretty-prints, so this requires fairly aggressive treatment.
+ *
+ * In particular, note that Tidy converts <pre>x</pre> to <pre>\nx\n</pre>,
+ * which theoretically affects display since the second line break is not
+ * ignored by compliant HTML parsers.
+ *
+ * This function also removes empty elements, as does Tidy.
+ */
+ protected function trimWhitespace() {
+ foreach ( $this->xpath->query( '//text()' ) as $child ) {
+ if ( strtolower( $child->parentNode->nodeName ) === 'pre' ) {
+ // Just trim one line break from the start and end
+ if ( substr_compare( $child->data, "\n", 0 ) === 0 ) {
+ $child->data = substr( $child->data, 1 );
+ }
+ if ( substr_compare( $child->data, "\n", -1 ) === 0 ) {
+ $child->data = substr( $child->data, 0, -1 );
+ }
+ } else {
+ // Trim all whitespace
+ $child->data = trim( $child->data );
+ }
+ if ( $child->data === '' ) {
+ $child->parentNode->removeChild( $child );
+ }
+ }
+ }
+
+ /**
+ * Serialize the XML DOM for comparison purposes. This does not generate HTML.
+ */
+ protected function serialize() {
+ return strtr( $this->doc->saveXML( $this->body ),
+ [ '<body>' => '', '</body>' => '' ] );
+ }
+}
+
--- /dev/null
+<?php
+/**
+ * Generic backend for the MediaWiki parser test suite, used by both the
+ * standalone parserTests.php and the PHPUnit "parsertests" suite.
+ *
+ * Copyright © 2004, 2010 Brion Vibber <brion@pobox.com>
+ * https://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @todo Make this more independent of the configuration (and if possible the database)
+ * @file
+ * @ingroup Testing
+ */
+use MediaWiki\MediaWikiServices;
+
+/**
+ * @ingroup Testing
+ */
+class ParserTestRunner {
+ /**
+ * @var bool $useTemporaryTables Use temporary tables for the temporary database
+ */
+ private $useTemporaryTables = true;
+
+ /**
+ * @var array $setupDone The status of each setup function
+ */
+ private $setupDone = [
+ 'staticSetup' => false,
+ 'perTestSetup' => false,
+ 'setupDatabase' => false,
+ 'setDatabase' => false,
+ 'setupUploads' => false,
+ ];
+
+ /**
+ * Our connection to the database
+ * @var DatabaseBase
+ */
+ private $db;
+
+ /**
+ * Database clone helper
+ * @var CloneDatabase
+ */
+ private $dbClone;
+
+ /**
+ * @var DjVuSupport
+ */
+ private $djVuSupport;
+
+ /**
+ * @var TidySupport
+ */
+ private $tidySupport;
+
+ /**
+ * @var TidyDriverBase
+ */
+ private $tidyDriver = null;
+
+ /**
+ * @var TestRecorder
+ */
+ private $recorder;
+
+ /**
+ * The upload directory, or null to not set up an upload directory
+ *
+ * @var string|null
+ */
+ private $uploadDir = null;
+
+ /**
+ * The name of the file backend to use, or null to use MockFileBackend.
+ * @var string|null
+ */
+ private $fileBackendName;
+
+ /**
+ * A complete regex for filtering tests.
+ * @var string
+ */
+ private $regex;
+
+ /**
+ * A list of normalization functions to apply to the expected and actual
+ * output.
+ * @var array
+ */
+ private $normalizationFunctions = [];
+
+ /**
+ * @param TestRecorder $recorder
+ * @param array $options
+ */
+ public function __construct( TestRecorder $recorder, $options = [] ) {
+ $this->recorder = $recorder;
+
+ if ( isset( $options['norm'] ) ) {
+ foreach ( $options['norm'] as $func ) {
+ if ( in_array( $func, [ 'removeTbody', 'trimWhitespace' ] ) ) {
+ $this->normalizationFunctions[] = $func;
+ } else {
+ $this->recorder->warning(
+ "Warning: unknown normalization option \"$func\"\n" );
+ }
+ }
+ }
+
+ if ( isset( $options['regex'] ) && $options['regex'] !== false ) {
+ $this->regex = $options['regex'];
+ } else {
+ # Matches anything
+ $this->regex = '//';
+ }
+
+ $this->keepUploads = !empty( $options['keep-uploads'] );
+
+ $this->fileBackendName = isset( $options['file-backend'] ) ?
+ $options['file-backend'] : false;
+
+ $this->runDisabled = !empty( $options['run-disabled'] );
+ $this->runParsoid = !empty( $options['run-parsoid'] );
+
+ $this->djVuSupport = new DjVuSupport();
+ $this->tidySupport = new TidySupport( !empty( $options['use-tidy-config'] ) );
+ if ( !$this->tidySupport->isEnabled() ) {
+ $this->recorder->warning(
+ "Warning: tidy is not installed, skipping some tests\n" );
+ }
+
+ if ( isset( $options['upload-dir'] ) ) {
+ $this->uploadDir = $options['upload-dir'];
+ }
+ }
+
+ public function getRecorder() {
+ return $this->recorder;
+ }
+
+ /**
+ * Do any setup which can be done once for all tests, independent of test
+ * options, except for database setup.
+ *
+ * Public setup functions in this class return a ScopedCallback object. When
+ * this object is destroyed by going out of scope, teardown of the
+ * corresponding test setup is performed.
+ *
+ * Teardown objects may be chained by passing a ScopedCallback from a
+ * previous setup stage as the $nextTeardown parameter. This enforces the
+ * convention that teardown actions are taken in reverse order to the
+ * corresponding setup actions. When $nextTeardown is specified, a
+ * ScopedCallback will be returned which first tears down the current
+ * setup stage, and then tears down the previous setup stage which was
+ * specified by $nextTeardown.
+ *
+ * @param ScopedCallback|null $nextTeardown
+ * @return ScopedCallback
+ */
+ public function staticSetup( $nextTeardown = null ) {
+ // A note on coding style:
+
+ // The general idea here is to keep setup code together with
+ // corresponding teardown code, in a fine-grained manner. We have two
+ // arrays: $setup and $teardown. The code snippets in the $setup array
+ // are executed at the end of the method, before it returns, and the
+ // code snippets in the $teardown array are executed in reverse order
+ // when the ScopedCallback object is consumed.
+
+ // Because it is a common operation to save, set and restore global
+ // variables, we have an additional convention: when the array key of
+ // $setup is a string, the string is taken to be the name of the global
+ // variable, and the element value is taken to be the desired new value.
+
+ // It's acceptable to just do the setup immediately, instead of adding
+ // a closure to $setup, except when the setup action depends on global
+ // variable initialisation being done first. In this case, you have to
+ // append a closure to $setup after the global variable is appended.
+
+ // When you add to setup functions in this class, please keep associated
+ // setup and teardown actions together in the source code, and please
+ // add comments explaining why the setup action is necessary.
+
+ $setup = [];
+ $teardown = [];
+
+ $teardown[] = $this->markSetupDone( 'staticSetup' );
+
+ // Some settings which influence HTML output
+ $setup['wgSitename'] = 'MediaWiki';
+ $setup['wgServer'] = 'http://example.org';
+ $setup['wgServerName'] = 'example.org';
+ $setup['wgScriptPath'] = '';
+ $setup['wgScript'] = '/index.php';
+ $setup['wgResourceBasePath'] = '';
+ $setup['wgStylePath'] = '/skins';
+ $setup['wgExtensionAssetsPath'] = '/extensions';
+ $setup['wgArticlePath'] = '/wiki/$1';
+ $setup['wgActionPaths'] = [];
+ $setup['wgVariantArticlePath'] = false;
+ $setup['wgUploadNavigationUrl'] = false;
+ $setup['wgCapitalLinks'] = true;
+ $setup['wgNoFollowLinks'] = true;
+ $setup['wgNoFollowDomainExceptions'] = [ 'no-nofollow.org' ];
+ $setup['wgExternalLinkTarget'] = false;
+ $setup['wgExperimentalHtmlIds'] = false;
+ $setup['wgLocaltimezone'] = 'UTC';
+ $setup['wgHtml5'] = true;
+ $setup['wgDisableLangConversion'] = false;
+ $setup['wgDisableTitleConversion'] = false;
+
+ // "extra language links"
+ // see https://gerrit.wikimedia.org/r/111390
+ $setup['wgExtraInterlanguageLinkPrefixes'] = [ 'mul' ];
+
+ // All FileRepo changes should be done here by injecting services,
+ // there should be no need to change global variables.
+ RepoGroup::setSingleton( $this->createRepoGroup() );
+ $teardown[] = function () {
+ RepoGroup::destroySingleton();
+ };
+
+ // Set up null lock managers
+ $setup['wgLockManagers'] = [ [
+ 'name' => 'fsLockManager',
+ 'class' => 'NullLockManager',
+ ], [
+ 'name' => 'nullLockManager',
+ 'class' => 'NullLockManager',
+ ] ];
+ $reset = function() {
+ LockManagerGroup::destroySingletons();
+ };
+ $setup[] = $reset;
+ $teardown[] = $reset;
+
+ // This allows article insertion into the prefixed DB
+ $setup['wgDefaultExternalStore'] = false;
+
+ // This might slightly reduce memory usage
+ $setup['wgAdaptiveMessageCache'] = true;
+
+ // This is essential and overrides disabling of database messages in TestSetup
+ $setup['wgUseDatabaseMessages'] = true;
+ $reset = function () {
+ MessageCache::destroyInstance();
+ };
+ $setup[] = $reset;
+ $teardown[] = $reset;
+
+ // It's not necessary to actually convert any files
+ $setup['wgSVGConverter'] = 'null';
+ $setup['wgSVGConverters'] = [ 'null' => 'echo "1">$output' ];
+
+ // Fake constant timestamp
+ Hooks::register( 'ParserGetVariableValueTs', 'ParserTestRunner::getFakeTimestamp' );
+ $teardown[] = function () {
+ Hooks::clear( 'ParserGetVariableValueTs' );
+ };
+
+ $this->appendNamespaceSetup( $setup, $teardown );
+
+ // Set up interwikis and append teardown function
+ $teardown[] = $this->setupInterwikis();
+
+ // This affects title normalization in links. It invalidates
+ // MediaWikiTitleCodec objects.
+ $setup['wgLocalInterwikis'] = [ 'local', 'mi' ];
+ $reset = function () {
+ $this->resetTitleServices();
+ };
+ $setup[] = $reset;
+ $teardown[] = $reset;
+
+ // Set up a mock MediaHandlerFactory
+ MediaWikiServices::getInstance()->disableService( 'MediaHandlerFactory' );
+ MediaWikiServices::getInstance()->redefineService(
+ 'MediaHandlerFactory',
+ function() {
+ return new MockMediaHandlerFactory();
+ }
+ );
+ $teardown[] = function () {
+ MediaWikiServices::getInstance()->resetServiceForTesting( 'MediaHandlerFactory' );
+ };
+
+ // SqlBagOStuff broke when using temporary tables on r40209 (bug 15892).
+ // It seems to have been fixed since (r55079?), but regressed at some point before r85701.
+ // This works around it for now...
+ global $wgObjectCaches;
+ $setup['wgObjectCaches'] = [ CACHE_DB => $wgObjectCaches['hash'] ] + $wgObjectCaches;
+ if ( isset( ObjectCache::$instances[CACHE_DB] ) ) {
+ $savedCache = ObjectCache::$instances[CACHE_DB];
+ ObjectCache::$instances[CACHE_DB] = new HashBagOStuff;
+ $teardown[] = function () use ( $savedCache ) {
+ ObjectCache::$instances[CACHE_DB] = $savedCache;
+ };
+ }
+
+ $teardown[] = $this->executeSetupSnippets( $setup );
+
+ // Schedule teardown snippets in reverse order
+ return $this->createTeardownObject( $teardown, $nextTeardown );
+ }
+
+ private function appendNamespaceSetup( &$setup, &$teardown ) {
+ // Add a namespace shadowing a interwiki link, to test
+ // proper precedence when resolving links. (bug 51680)
+ $setup['wgExtraNamespaces'] = [
+ 100 => 'MemoryAlpha',
+ 101 => 'MemoryAlpha_talk'
+ ];
+ // Changing wgExtraNamespaces invalidates caches in MWNamespace and
+ // any live Language object, both on setup and teardown
+ $reset = function () {
+ MWNamespace::getCanonicalNamespaces( true );
+ $GLOBALS['wgContLang']->resetNamespaces();
+ };
+ $setup[] = $reset;
+ $teardown[] = $reset;
+ }
+
+ /**
+ * Create a RepoGroup object appropriate for the current configuration
+ * @return RepoGroup
+ */
+ protected function createRepoGroup() {
+ if ( $this->uploadDir ) {
+ if ( $this->fileBackendName ) {
+ throw new MWException( 'You cannot specify both use-filebackend and upload-dir' );
+ }
+ $backend = new FSFileBackend( [
+ 'name' => 'local-backend',
+ 'wikiId' => wfWikiID(),
+ 'basePath' => $this->uploadDir
+ ] );
+ } elseif ( $this->fileBackendName ) {
+ global $wgFileBackends;
+ $name = $this->fileBackendName;
+ $useConfig = false;
+ foreach ( $wgFileBackends as $conf ) {
+ if ( $conf['name'] === $name ) {
+ $useConfig = $conf;
+ }
+ }
+ if ( $useConfig === false ) {
+ throw new MWException( "Unable to find file backend \"$name\"" );
+ }
+ $useConfig['name'] = 'local-backend'; // swap name
+ unset( $useConfig['lockManager'] );
+ unset( $useConfig['fileJournal'] );
+ $class = $useConfig['class'];
+ $backend = new $class( $useConfig );
+ } else {
+ # Replace with a mock. We do not care about generating real
+ # files on the filesystem, just need to expose the file
+ # informations.
+ $backend = new MockFileBackend( [
+ 'name' => 'local-backend',
+ 'wikiId' => wfWikiID()
+ ] );
+ }
+
+ return new RepoGroup(
+ [
+ 'class' => 'LocalRepo',
+ 'name' => 'local',
+ 'url' => 'http://example.com/images',
+ 'hashLevels' => 2,
+ 'transformVia404' => false,
+ 'backend' => $backend
+ ],
+ []
+ );
+ }
+
+ /**
+ * Execute an array in which elements with integer keys are taken to be
+ * callable objects, and other elements are taken to be global variable
+ * set operations, with the key giving the variable name and the value
+ * giving the new global variable value. A closure is returned which, when
+ * executed, sets the global variables back to the values they had before
+ * this function was called.
+ *
+ * @see staticSetup
+ *
+ * @param array $setup
+ * @return closure
+ */
+ protected function executeSetupSnippets( $setup ) {
+ $saved = [];
+ foreach ( $setup as $name => $value ) {
+ if ( is_int( $name ) ) {
+ $value();
+ } else {
+ $saved[$name] = isset( $GLOBALS[$name] ) ? $GLOBALS[$name] : null;
+ $GLOBALS[$name] = $value;
+ }
+ }
+ return function () use ( $saved ) {
+ $this->executeSetupSnippets( $saved );
+ };
+ }
+
+ /**
+ * Take a setup array in the same format as the one given to
+ * executeSetupSnippets(), and return a ScopedCallback which, when consumed,
+ * executes the snippets in the setup array in reverse order. This is used
+ * to create "teardown objects" for the public API.
+ *
+ * @see staticSetup
+ *
+ * @param array $teardown The snippet array
+ * @param ScopedCallback|null A ScopedCallback to consume
+ * @return ScopedCallback
+ */
+ protected function createTeardownObject( $teardown, $nextTeardown ) {
+ return new ScopedCallback( function() use ( $teardown, $nextTeardown ) {
+ // Schedule teardown snippets in reverse order
+ $teardown = array_reverse( $teardown );
+
+ $this->executeSetupSnippets( $teardown );
+ if ( $nextTeardown ) {
+ ScopedCallback::consume( $nextTeardown );
+ }
+ } );
+ }
+
+ /**
+ * Set a setupDone flag to indicate that setup has been done, and return
+ * the teardown closure. If the flag was already set, throw an exception.
+ *
+ * @param string $funcName The setup function name
+ * @return closure
+ */
+ protected function markSetupDone( $funcName ) {
+ if ( $this->setupDone[$funcName] ) {
+ throw new MWException( "$funcName is already done" );
+ }
+ $this->setupDone[$funcName] = true;
+ return function () use ( $funcName ) {
+ wfDebug( "markSetupDone unmarked $funcName" );
+ $this->setupDone[$funcName] = false;
+ };
+ }
+
+ /**
+ * Ensure a given setup stage has been done, throw an exception if it has
+ * not.
+ */
+ protected function checkSetupDone( $funcName, $funcName2 = null ) {
+ if ( !$this->setupDone[$funcName]
+ && ( $funcName === null || !$this->setupDone[$funcName2] )
+ ) {
+ throw new MWException( "$funcName must be called before calling " .
+ wfGetCaller() );
+ }
+ }
+
+ /**
+ * Determine whether a particular setup function has been run
+ *
+ * @param string $funcName
+ * @return boolean
+ */
+ public function isSetupDone( $funcName ) {
+ return isset( $this->setupDone[$funcName] ) ? $this->setupDone[$funcName] : false;
+ }
+
+ /**
+ * Insert hardcoded interwiki in the lookup table.
+ *
+ * This function insert a set of well known interwikis that are used in
+ * the parser tests. They can be considered has fixtures are injected in
+ * the interwiki cache by using the 'InterwikiLoadPrefix' hook.
+ * Since we are not interested in looking up interwikis in the database,
+ * the hook completely replace the existing mechanism (hook returns false).
+ *
+ * @return closure for teardown
+ */
+ private function setupInterwikis() {
+ # Hack: insert a few Wikipedia in-project interwiki prefixes,
+ # for testing inter-language links
+ Hooks::register( 'InterwikiLoadPrefix', function ( $prefix, &$iwData ) {
+ static $testInterwikis = [
+ 'local' => [
+ 'iw_url' => 'http://doesnt.matter.org/$1',
+ 'iw_api' => '',
+ 'iw_wikiid' => '',
+ 'iw_local' => 0 ],
+ 'wikipedia' => [
+ 'iw_url' => 'http://en.wikipedia.org/wiki/$1',
+ 'iw_api' => '',
+ 'iw_wikiid' => '',
+ 'iw_local' => 0 ],
+ 'meatball' => [
+ 'iw_url' => 'http://www.usemod.com/cgi-bin/mb.pl?$1',
+ 'iw_api' => '',
+ 'iw_wikiid' => '',
+ 'iw_local' => 0 ],
+ 'memoryalpha' => [
+ 'iw_url' => 'http://www.memory-alpha.org/en/index.php/$1',
+ 'iw_api' => '',
+ 'iw_wikiid' => '',
+ 'iw_local' => 0 ],
+ 'zh' => [
+ 'iw_url' => 'http://zh.wikipedia.org/wiki/$1',
+ 'iw_api' => '',
+ 'iw_wikiid' => '',
+ 'iw_local' => 1 ],
+ 'es' => [
+ 'iw_url' => 'http://es.wikipedia.org/wiki/$1',
+ 'iw_api' => '',
+ 'iw_wikiid' => '',
+ 'iw_local' => 1 ],
+ 'fr' => [
+ 'iw_url' => 'http://fr.wikipedia.org/wiki/$1',
+ 'iw_api' => '',
+ 'iw_wikiid' => '',
+ 'iw_local' => 1 ],
+ 'ru' => [
+ 'iw_url' => 'http://ru.wikipedia.org/wiki/$1',
+ 'iw_api' => '',
+ 'iw_wikiid' => '',
+ 'iw_local' => 1 ],
+ 'mi' => [
+ 'iw_url' => 'http://mi.wikipedia.org/wiki/$1',
+ 'iw_api' => '',
+ 'iw_wikiid' => '',
+ 'iw_local' => 1 ],
+ 'mul' => [
+ 'iw_url' => 'http://wikisource.org/wiki/$1',
+ 'iw_api' => '',
+ 'iw_wikiid' => '',
+ 'iw_local' => 1 ],
+ ];
+ if ( array_key_exists( $prefix, $testInterwikis ) ) {
+ $iwData = $testInterwikis[$prefix];
+ }
+
+ // We only want to rely on the above fixtures
+ return false;
+ } );// hooks::register
+
+ return function () {
+ // Tear down
+ Hooks::clear( 'InterwikiLoadPrefix' );
+ };
+ }
+
+ /**
+ * Reset the Title-related services that need resetting
+ * for each test
+ */
+ private function resetTitleServices() {
+ $services = MediaWikiServices::getInstance();
+ $services->resetServiceForTesting( 'TitleFormatter' );
+ $services->resetServiceForTesting( 'TitleParser' );
+ $services->resetServiceForTesting( '_MediaWikiTitleCodec' );
+ $services->resetServiceForTesting( 'LinkRenderer' );
+ $services->resetServiceForTesting( 'LinkRendererFactory' );
+ }
+
+ /**
+ * Remove last character if it is a newline
+ * @group utility
+ * @param string $s
+ * @return string
+ */
+ public static function chomp( $s ) {
+ if ( substr( $s, -1 ) === "\n" ) {
+ return substr( $s, 0, -1 );
+ } else {
+ return $s;
+ }
+ }
+
+ /**
+ * Run a series of tests listed in the given text files.
+ * Each test consists of a brief description, wikitext input,
+ * and the expected HTML output.
+ *
+ * Prints status updates on stdout and counts up the total
+ * number and percentage of passed tests.
+ *
+ * Handles all setup and teardown.
+ *
+ * @param array $filenames Array of strings
+ * @return bool True if passed all tests, false if any tests failed.
+ */
+ public function runTestsFromFiles( $filenames ) {
+ $ok = false;
+
+ $teardownGuard = $this->staticSetup();
+ $teardownGuard = $this->setupDatabase( $teardownGuard );
+ $teardownGuard = $this->setupUploads( $teardownGuard );
+
+ $this->recorder->start();
+ try {
+ $ok = true;
+
+ foreach ( $filenames as $filename ) {
+ $testFileInfo = TestFileReader::read( $filename, [
+ 'runDisabled' => $this->runDisabled,
+ 'runParsoid' => $this->runParsoid,
+ 'regex' => $this->regex ] );
+
+ // Don't start the suite if there are no enabled tests in the file
+ if ( !$testFileInfo['tests'] ) {
+ continue;
+ }
+
+ $this->recorder->startSuite( $filename );
+ $ok = $this->runTests( $testFileInfo ) && $ok;
+ $this->recorder->endSuite( $filename );
+ }
+
+ $this->recorder->report();
+ } catch ( DBError $e ) {
+ $this->recorder->warning( $e->getMessage() );
+ }
+ $this->recorder->end();
+
+ ScopedCallback::consume( $teardownGuard );
+
+ return $ok;
+ }
+
+ /**
+ * Determine whether the current parser has the hooks registered in it
+ * that are required by a file read by TestFileReader.
+ */
+ public function meetsRequirements( $requirements ) {
+ foreach ( $requirements as $requirement ) {
+ switch ( $requirement['type'] ) {
+ case 'hook':
+ $ok = $this->requireHook( $requirement['name'] );
+ break;
+ case 'functionHook':
+ $ok = $this->requireFunctionHook( $requirement['name'] );
+ break;
+ case 'transparentHook':
+ $ok = $this->requireTransparentHook( $requirement['name'] );
+ break;
+ }
+ if ( !$ok ) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Run the tests from a single file. staticSetup() and setupDatabase()
+ * must have been called already.
+ *
+ * @param array $testFileInfo Parsed file info returned by TestFileReader
+ * @return bool True if passed all tests, false if any tests failed.
+ */
+ public function runTests( $testFileInfo ) {
+ $ok = true;
+
+ $this->checkSetupDone( 'staticSetup' );
+
+ // Don't add articles from the file if there are no enabled tests from the file
+ if ( !$testFileInfo['tests'] ) {
+ return true;
+ }
+
+ // If any requirements are not met, mark all tests from the file as skipped
+ if ( !$this->meetsRequirements( $testFileInfo['requirements'] ) ) {
+ foreach ( $testFileInfo['tests'] as $test ) {
+ $this->recorder->startTest( $test );
+ $this->recorder->skipped( $test, 'required extension not enabled' );
+ }
+ return true;
+ }
+
+ // Add articles
+ $this->addArticles( $testFileInfo['articles'] );
+
+ // Run tests
+ foreach ( $testFileInfo['tests'] as $test ) {
+ $this->recorder->startTest( $test );
+ $result =
+ $this->runTest( $test );
+ if ( $result !== false ) {
+ $ok = $ok && $result->isSuccess();
+ $this->recorder->record( $test, $result );
+ }
+ }
+
+ return $ok;
+ }
+
+ /**
+ * Get a Parser object
+ *
+ * @param string $preprocessor
+ * @return Parser
+ */
+ function getParser( $preprocessor = null ) {
+ global $wgParserConf;
+
+ $class = $wgParserConf['class'];
+ $parser = new $class( [ 'preprocessorClass' => $preprocessor ] + $wgParserConf );
+ ParserTestParserHook::setup( $parser );
+
+ return $parser;
+ }
+
+ /**
+ * Run a given wikitext input through a freshly-constructed wiki parser,
+ * and compare the output against the expected results.
+ * Prints status and explanatory messages to stdout.
+ *
+ * staticSetup() and setupWikiData() must be called before this function
+ * is entered.
+ *
+ * @param array $test The test parameters:
+ * - test: The test name
+ * - desc: The subtest description
+ * - input: Wikitext to try rendering
+ * - options: Array of test options
+ * - config: Overrides for global variables, one per line
+ *
+ * @return ParserTestResult or false if skipped
+ */
+ public function runTest( $test ) {
+ wfDebug( __METHOD__.": running {$test['desc']}" );
+ $opts = $this->parseOptions( $test['options'] );
+ $teardownGuard = $this->perTestSetup( $test );
+
+ $context = RequestContext::getMain();
+ $user = $context->getUser();
+ $options = ParserOptions::newFromContext( $context );
+
+ if ( isset( $opts['djvu'] ) ) {
+ if ( !$this->djVuSupport->isEnabled() ) {
+ $this->recorder->skipped( $test,
+ 'djvu binaries do not exist or are not executable' );
+ return false;
+ }
+ }
+
+ if ( isset( $opts['tidy'] ) ) {
+ if ( !$this->tidySupport->isEnabled() ) {
+ $this->recorder->skipped( $test, 'tidy extension is not installed' );
+ return false;
+ } else {
+ $options->setTidy( true );
+ }
+ }
+
+ if ( isset( $opts['title'] ) ) {
+ $titleText = $opts['title'];
+ } else {
+ $titleText = 'Parser test';
+ }
+
+ $local = isset( $opts['local'] );
+ $preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null;
+ $parser = $this->getParser( $preprocessor );
+ $title = Title::newFromText( $titleText );
+
+ if ( isset( $opts['pst'] ) ) {
+ $out = $parser->preSaveTransform( $test['input'], $title, $user, $options );
+ } elseif ( isset( $opts['msg'] ) ) {
+ $out = $parser->transformMsg( $test['input'], $options, $title );
+ } elseif ( isset( $opts['section'] ) ) {
+ $section = $opts['section'];
+ $out = $parser->getSection( $test['input'], $section );
+ } elseif ( isset( $opts['replace'] ) ) {
+ $section = $opts['replace'][0];
+ $replace = $opts['replace'][1];
+ $out = $parser->replaceSection( $test['input'], $section, $replace );
+ } elseif ( isset( $opts['comment'] ) ) {
+ $out = Linker::formatComment( $test['input'], $title, $local );
+ } elseif ( isset( $opts['preload'] ) ) {
+ $out = $parser->getPreloadText( $test['input'], $title, $options );
+ } else {
+ $output = $parser->parse( $test['input'], $title, $options, true, true, 1337 );
+ $output->setTOCEnabled( !isset( $opts['notoc'] ) );
+ $out = $output->getText();
+ if ( isset( $opts['tidy'] ) ) {
+ $out = preg_replace( '/\s+$/', '', $out );
+ }
+
+ if ( isset( $opts['showtitle'] ) ) {
+ if ( $output->getTitleText() ) {
+ $title = $output->getTitleText();
+ }
+
+ $out = "$title\n$out";
+ }
+
+ if ( isset( $opts['showindicators'] ) ) {
+ $indicators = '';
+ foreach ( $output->getIndicators() as $id => $content ) {
+ $indicators .= "$id=$content\n";
+ }
+ $out = $indicators . $out;
+ }
+
+ if ( isset( $opts['ill'] ) ) {
+ $out = implode( ' ', $output->getLanguageLinks() );
+ } elseif ( isset( $opts['cat'] ) ) {
+ $out = '';
+ foreach ( $output->getCategories() as $name => $sortkey ) {
+ if ( $out !== '' ) {
+ $out .= "\n";
+ }
+ $out .= "cat=$name sort=$sortkey";
+ }
+ }
+ }
+
+ ScopedCallback::consume( $teardownGuard );
+
+ $expected = $test['result'];
+ if ( count( $this->normalizationFunctions ) ) {
+ $expected = ParserTestResultNormalizer::normalize(
+ $test['expected'], $this->normalizationFunctions );
+ $out = ParserTestResultNormalizer::normalize( $out, $this->normalizationFunctions );
+ }
+
+ $testResult = new ParserTestResult( $test, $expected, $out );
+ return $testResult;
+ }
+
+ /**
+ * Use a regex to find out the value of an option
+ * @param string $key Name of option val to retrieve
+ * @param array $opts Options array to look in
+ * @param mixed $default Default value returned if not found
+ * @return mixed
+ */
+ private static function getOptionValue( $key, $opts, $default ) {
+ $key = strtolower( $key );
+
+ if ( isset( $opts[$key] ) ) {
+ return $opts[$key];
+ } else {
+ return $default;
+ }
+ }
+
+ /**
+ * Given the options string, return an associative array of options.
+ * @todo Move this to TestFileReader
+ *
+ * @param string $instring
+ * @return array
+ */
+ private function parseOptions( $instring ) {
+ $opts = [];
+ // foo
+ // foo=bar
+ // foo="bar baz"
+ // foo=[[bar baz]]
+ // foo=bar,"baz quux"
+ // foo={...json...}
+ $defs = '(?(DEFINE)
+ (?<qstr> # Quoted string
+ "
+ (?:[^\\\\"] | \\\\.)*
+ "
+ )
+ (?<json>
+ \{ # Open bracket
+ (?:
+ [^"{}] | # Not a quoted string or object, or
+ (?&qstr) | # A quoted string, or
+ (?&json) # A json object (recursively)
+ )*
+ \} # Close bracket
+ )
+ (?<value>
+ (?:
+ (?&qstr) # Quoted val
+ |
+ \[\[
+ [^]]* # Link target
+ \]\]
+ |
+ [\w-]+ # Plain word
+ |
+ (?&json) # JSON object
+ )
+ )
+ )';
+ $regex = '/' . $defs . '\b
+ (?<k>[\w-]+) # Key
+ \b
+ (?:\s*
+ = # First sub-value
+ \s*
+ (?<v>
+ (?&value)
+ (?:\s*
+ , # Sub-vals 1..N
+ \s*
+ (?&value)
+ )*
+ )
+ )?
+ /x';
+ $valueregex = '/' . $defs . '(?&value)/x';
+
+ if ( preg_match_all( $regex, $instring, $matches, PREG_SET_ORDER ) ) {
+ foreach ( $matches as $bits ) {
+ $key = strtolower( $bits['k'] );
+ if ( !isset( $bits['v'] ) ) {
+ $opts[$key] = true;
+ } else {
+ preg_match_all( $valueregex, $bits['v'], $vmatches );
+ $opts[$key] = array_map( [ $this, 'cleanupOption' ], $vmatches[0] );
+ if ( count( $opts[$key] ) == 1 ) {
+ $opts[$key] = $opts[$key][0];
+ }
+ }
+ }
+ }
+ return $opts;
+ }
+
+ private function cleanupOption( $opt ) {
+ if ( substr( $opt, 0, 1 ) == '"' ) {
+ return stripcslashes( substr( $opt, 1, -1 ) );
+ }
+
+ if ( substr( $opt, 0, 2 ) == '[[' ) {
+ return substr( $opt, 2, -2 );
+ }
+
+ if ( substr( $opt, 0, 1 ) == '{' ) {
+ return FormatJson::decode( $opt, true );
+ }
+ return $opt;
+ }
+
+ /**
+ * Do any required setup which is dependent on test options.
+ *
+ * @see staticSetup() for more information about setup/teardown
+ *
+ * @param array $test Test info supplied by TestFileReader
+ * @param callable|null $nextTeardown
+ * @return ScopedCallback
+ */
+ public function perTestSetup( $test, $nextTeardown = null ) {
+ $teardown = [];
+
+ $this->checkSetupDone( 'setupDatabase', 'setDatabase' );
+ $teardown[] = $this->markSetupDone( 'perTestSetup' );
+
+ $opts = $this->parseOptions( $test['options'] );
+ $config = $test['config'];
+
+ // Find out values for some special options.
+ $langCode =
+ self::getOptionValue( 'language', $opts, 'en' );
+ $variant =
+ self::getOptionValue( 'variant', $opts, false );
+ $maxtoclevel =
+ self::getOptionValue( 'wgMaxTocLevel', $opts, 999 );
+ $linkHolderBatchSize =
+ self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 );
+
+ $setup = [
+ 'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ),
+ 'wgLanguageCode' => $langCode,
+ 'wgRawHtml' => self::getOptionValue( 'wgRawHtml', $opts, false ),
+ 'wgNamespacesWithSubpages' => [ 0 => isset( $opts['subpage'] ) ],
+ 'wgMaxTocLevel' => $maxtoclevel,
+ 'wgAllowExternalImages' => self::getOptionValue( 'wgAllowExternalImages', $opts, true ),
+ 'wgThumbLimits' => [ self::getOptionValue( 'thumbsize', $opts, 180 ) ],
+ 'wgDefaultLanguageVariant' => $variant,
+ 'wgLinkHolderBatchSize' => $linkHolderBatchSize,
+ // Set as a JSON object like:
+ // wgEnableMagicLinks={"ISBN":false, "PMID":false, "RFC":false}
+ 'wgEnableMagicLinks' => self::getOptionValue( 'wgEnableMagicLinks', $opts, [] )
+ + [ 'ISBN' => true, 'PMID' => true, 'RFC' => true ],
+ ];
+
+ if ( $config ) {
+ $configLines = explode( "\n", $config );
+
+ foreach ( $configLines as $line ) {
+ list( $var, $value ) = explode( '=', $line, 2 );
+ $setup[$var] = eval( "return $value;" );
+ }
+ }
+
+ /** @since 1.20 */
+ Hooks::run( 'ParserTestGlobals', [ &$setup ] );
+
+ // Create tidy driver
+ if ( isset( $opts['tidy'] ) ) {
+ // Cache a driver instance
+ if ( $this->tidyDriver === null ) {
+ $this->tidyDriver = MWTidy::factory( $this->tidySupport->getConfig() );
+ }
+ $tidy = $this->tidyDriver;
+ } else {
+ $tidy = false;
+ }
+ MWTidy::setInstance( $tidy );
+ $teardown[] = function () {
+ MWTidy::destroySingleton();
+ };
+
+ // Set content language. This invalidates the magic word cache and title services
+ wfDebug( "Setting up language $langCode" );
+ $lang = Language::factory( $langCode );
+ $setup['wgContLang'] = $lang;
+ $reset = function () {
+ MagicWord::clearCache();
+ $this->resetTitleServices();
+ };
+ $setup[] = $reset;
+ $teardown[] = $reset;
+
+ // Make a user object with the same language
+ $user = new User;
+ $user->setOption( 'language', $langCode );
+ $setup['wgLang'] = $lang;
+
+ // We (re)set $wgThumbLimits to a single-element array above.
+ $user->setOption( 'thumbsize', 0 );
+
+ $setup['wgUser'] = $user;
+
+ // And put both user and language into the context
+ $context = RequestContext::getMain();
+ $context->setUser( $user );
+ $context->setLanguage( $lang );
+ $teardown[] = function () use ( $context ) {
+ // Reset context to the restored globals
+ $context->setUser( $GLOBALS['wgUser'] );
+ $context->setLanguage( $GLOBALS['wgContLang'] );
+ };
+
+ $teardown[] = $this->executeSetupSnippets( $setup );
+
+ return $this->createTeardownObject( $teardown, $nextTeardown );
+ }
+
+ /**
+ * List of temporary tables to create, without prefix.
+ * Some of these probably aren't necessary.
+ * @return array
+ */
+ private function listTables() {
+ $tables = [ 'user', 'user_properties', 'user_former_groups', 'page', 'page_restrictions',
+ 'protected_titles', 'revision', 'text', 'pagelinks', 'imagelinks',
+ 'categorylinks', 'templatelinks', 'externallinks', 'langlinks', 'iwlinks',
+ 'site_stats', 'ipblocks', 'image', 'oldimage',
+ 'recentchanges', 'watchlist', 'interwiki', 'logging', 'log_search',
+ 'querycache', 'objectcache', 'job', 'l10n_cache', 'redirect', 'querycachetwo',
+ 'archive', 'user_groups', 'page_props', 'category'
+ ];
+
+ if ( in_array( $this->db->getType(), [ 'mysql', 'sqlite', 'oracle' ] ) ) {
+ array_push( $tables, 'searchindex' );
+ }
+
+ // Allow extensions to add to the list of tables to duplicate;
+ // may be necessary if they hook into page save or other code
+ // which will require them while running tests.
+ Hooks::run( 'ParserTestTables', [ &$tables ] );
+
+ return $tables;
+ }
+
+ public function setDatabase( IDatabase $db ) {
+ $this->db = $db;
+ $this->setupDone['setDatabase'] = true;
+ }
+
+ /**
+ * Set up temporary DB tables.
+ *
+ * For best performance, call this once only for all tests. However, it can
+ * be called at the start of each test if more isolation is desired.
+ *
+ * @todo: This is basically an unrefactored copy of
+ * MediaWikiTestCase::setupAllTestDBs. They should be factored out somehow.
+ *
+ * Do not call this function from a MediaWikiTestCase subclass, since
+ * MediaWikiTestCase does its own DB setup. Instead use setDatabase().
+ *
+ * @see staticSetup() for more information about setup/teardown
+ *
+ * @param ScopedCallback|null $nextTeardown The next teardown object
+ * @return ScopedCallback The teardown object
+ */
+ public function setupDatabase( $nextTeardown = null ) {
+ global $wgDBprefix;
+
+ $this->db = wfGetDB( DB_MASTER );
+ $dbType = $this->db->getType();
+
+ if ( $dbType == 'oracle' ) {
+ $suspiciousPrefixes = [ 'pt_', MediaWikiTestCase::ORA_DB_PREFIX ];
+ } else {
+ $suspiciousPrefixes = [ 'parsertest_', MediaWikiTestCase::DB_PREFIX ];
+ }
+ if ( in_array( $wgDBprefix, $suspiciousPrefixes ) ) {
+ throw new MWException( "\$wgDBprefix=$wgDBprefix suggests DB setup is already done" );
+ }
+
+ $teardown = [];
+
+ $teardown[] = $this->markSetupDone( 'setupDatabase' );
+
+ # CREATE TEMPORARY TABLE breaks if there is more than one server
+ if ( wfGetLB()->getServerCount() != 1 ) {
+ $this->useTemporaryTables = false;
+ }
+
+ $temporary = $this->useTemporaryTables || $dbType == 'postgres';
+ $prefix = $dbType != 'oracle' ? 'parsertest_' : 'pt_';
+
+ $this->dbClone = new CloneDatabase( $this->db, $this->listTables(), $prefix );
+ $this->dbClone->useTemporaryTables( $temporary );
+ $this->dbClone->cloneTableStructure();
+
+ if ( $dbType == 'oracle' ) {
+ $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
+ # Insert 0 user to prevent FK violations
+
+ # Anonymous user
+ $this->db->insert( 'user', [
+ 'user_id' => 0,
+ 'user_name' => 'Anonymous' ] );
+ }
+
+ $teardown[] = function () {
+ $this->teardownDatabase();
+ };
+
+ // Wipe some DB query result caches on setup and teardown
+ $reset = function () {
+ LinkCache::singleton()->clear();
+
+ // Clear the message cache
+ MessageCache::singleton()->clear();
+ };
+ $reset();
+ $teardown[] = $reset;
+ return $this->createTeardownObject( $teardown, $nextTeardown );
+ }
+
+ /**
+ * Add data about uploads to the new test DB, and set up the upload
+ * directory. This should be called after either setDatabase() or
+ * setupDatabase().
+ *
+ * @param ScopedCallback|null $nextTeardown The next teardown object
+ * @return ScopedCallback The teardown object
+ */
+ public function setupUploads( $nextTeardown = null ) {
+ $teardown = [];
+
+ $this->checkSetupDone( 'setupDatabase', 'setDatabase' );
+ $teardown[] = $this->markSetupDone( 'setupUploads' );
+
+ // Create the files in the upload directory (or pretend to create them
+ // in a MockFileBackend). Append teardown callback.
+ $teardown[] = $this->setupUploadBackend();
+
+ // Create a user
+ $user = User::createNew( 'WikiSysop' );
+
+ // Register the uploads in the database
+
+ $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) );
+ # note that the size/width/height/bits/etc of the file
+ # are actually set by inspecting the file itself; the arguments
+ # to recordUpload2 have no effect. That said, we try to make things
+ # match up so it is less confusing to readers of the code & tests.
+ $image->recordUpload2( '', 'Upload of some lame file', 'Some lame file', [
+ 'size' => 7881,
+ 'width' => 1941,
+ 'height' => 220,
+ 'bits' => 8,
+ 'media_type' => MEDIATYPE_BITMAP,
+ 'mime' => 'image/jpeg',
+ 'metadata' => serialize( [] ),
+ 'sha1' => Wikimedia\base_convert( '1', 16, 36, 31 ),
+ 'fileExists' => true
+ ], $this->db->timestamp( '20010115123500' ), $user );
+
+ $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Thumb.png' ) );
+ # again, note that size/width/height below are ignored; see above.
+ $image->recordUpload2( '', 'Upload of some lame thumbnail', 'Some lame thumbnail', [
+ 'size' => 22589,
+ 'width' => 135,
+ 'height' => 135,
+ 'bits' => 8,
+ 'media_type' => MEDIATYPE_BITMAP,
+ 'mime' => 'image/png',
+ 'metadata' => serialize( [] ),
+ 'sha1' => Wikimedia\base_convert( '2', 16, 36, 31 ),
+ 'fileExists' => true
+ ], $this->db->timestamp( '20130225203040' ), $user );
+
+ $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.svg' ) );
+ $image->recordUpload2( '', 'Upload of some lame SVG', 'Some lame SVG', [
+ 'size' => 12345,
+ 'width' => 240,
+ 'height' => 180,
+ 'bits' => 0,
+ 'media_type' => MEDIATYPE_DRAWING,
+ 'mime' => 'image/svg+xml',
+ 'metadata' => serialize( [] ),
+ 'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
+ 'fileExists' => true
+ ], $this->db->timestamp( '20010115123500' ), $user );
+
+ # This image will be blacklisted in [[MediaWiki:Bad image list]]
+ $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) );
+ $image->recordUpload2( '', 'zomgnotcensored', 'Borderline image', [
+ 'size' => 12345,
+ 'width' => 320,
+ 'height' => 240,
+ 'bits' => 24,
+ 'media_type' => MEDIATYPE_BITMAP,
+ 'mime' => 'image/jpeg',
+ 'metadata' => serialize( [] ),
+ 'sha1' => Wikimedia\base_convert( '3', 16, 36, 31 ),
+ 'fileExists' => true
+ ], $this->db->timestamp( '20010115123500' ), $user );
+
+ $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Video.ogv' ) );
+ $image->recordUpload2( '', 'A pretty movie', 'Will it play', [
+ 'size' => 12345,
+ 'width' => 320,
+ 'height' => 240,
+ 'bits' => 0,
+ 'media_type' => MEDIATYPE_VIDEO,
+ 'mime' => 'application/ogg',
+ 'metadata' => serialize( [] ),
+ 'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
+ 'fileExists' => true
+ ], $this->db->timestamp( '20010115123500' ), $user );
+
+ $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Audio.oga' ) );
+ $image->recordUpload2( '', 'An awesome hitsong', 'Will it play', [
+ 'size' => 12345,
+ 'width' => 0,
+ 'height' => 0,
+ 'bits' => 0,
+ 'media_type' => MEDIATYPE_AUDIO,
+ 'mime' => 'application/ogg',
+ 'metadata' => serialize( [] ),
+ 'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
+ 'fileExists' => true
+ ], $this->db->timestamp( '20010115123500' ), $user );
+
+ # A DjVu file
+ $image = wfLocalFile( Title::makeTitle( NS_FILE, 'LoremIpsum.djvu' ) );
+ $image->recordUpload2( '', 'Upload a DjVu', 'A DjVu', [
+ 'size' => 3249,
+ 'width' => 2480,
+ 'height' => 3508,
+ 'bits' => 0,
+ 'media_type' => MEDIATYPE_BITMAP,
+ 'mime' => 'image/vnd.djvu',
+ 'metadata' => '<?xml version="1.0" ?>
+<!DOCTYPE DjVuXML PUBLIC "-//W3C//DTD DjVuXML 1.1//EN" "pubtext/DjVuXML-s.dtd">
+<DjVuXML>
+<HEAD></HEAD>
+<BODY><OBJECT height="3508" width="2480">
+<PARAM name="DPI" value="300" />
+<PARAM name="GAMMA" value="2.2" />
+</OBJECT>
+<OBJECT height="3508" width="2480">
+<PARAM name="DPI" value="300" />
+<PARAM name="GAMMA" value="2.2" />
+</OBJECT>
+<OBJECT height="3508" width="2480">
+<PARAM name="DPI" value="300" />
+<PARAM name="GAMMA" value="2.2" />
+</OBJECT>
+<OBJECT height="3508" width="2480">
+<PARAM name="DPI" value="300" />
+<PARAM name="GAMMA" value="2.2" />
+</OBJECT>
+<OBJECT height="3508" width="2480">
+<PARAM name="DPI" value="300" />
+<PARAM name="GAMMA" value="2.2" />
+</OBJECT>
+</BODY>
+</DjVuXML>',
+ 'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
+ 'fileExists' => true
+ ], $this->db->timestamp( '20010115123600' ), $user );
+
+ return $this->createTeardownObject( $teardown, $nextTeardown );
+ }
+
+ /**
+ * Helper for database teardown, called from the teardown closure. Destroy
+ * the database clone and fix up some things that CloneDatabase doesn't fix.
+ *
+ * @todo Move most things here to CloneDatabase
+ */
+ private function teardownDatabase() {
+ $this->checkSetupDone( 'setupDatabase' );
+
+ $this->dbClone->destroy();
+ $this->databaseSetupDone = false;
+
+ if ( $this->useTemporaryTables ) {
+ if ( $this->db->getType() == 'sqlite' ) {
+ # Under SQLite the searchindex table is virtual and need
+ # to be explicitly destroyed. See bug 29912
+ # See also MediaWikiTestCase::destroyDB()
+ wfDebug( __METHOD__ . " explicitly destroying sqlite virtual table parsertest_searchindex\n" );
+ $this->db->query( "DROP TABLE `parsertest_searchindex`" );
+ }
+ # Don't need to do anything
+ return;
+ }
+
+ $tables = $this->listTables();
+
+ foreach ( $tables as $table ) {
+ if ( $this->db->getType() == 'oracle' ) {
+ $this->db->query( "DROP TABLE pt_$table DROP CONSTRAINTS" );
+ } else {
+ $this->db->query( "DROP TABLE `parsertest_$table`" );
+ }
+ }
+
+ if ( $this->db->getType() == 'oracle' ) {
+ $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
+ }
+ }
+
+ /**
+ * Upload test files to the backend created by createRepoGroup().
+ *
+ * @return callable The teardown callback
+ */
+ private function setupUploadBackend() {
+ global $IP;
+
+ $repo = RepoGroup::singleton()->getLocalRepo();
+ $base = $repo->getZonePath( 'public' );
+ $backend = $repo->getBackend();
+ $backend->prepare( [ 'dir' => "$base/3/3a" ] );
+ $backend->store( [
+ 'src' => "$IP/tests/phpunit/data/parser/headbg.jpg",
+ 'dst' => "$base/3/3a/Foobar.jpg"
+ ] );
+ $backend->prepare( [ 'dir' => "$base/e/ea" ] );
+ $backend->store( [
+ 'src' => "$IP/tests/phpunit/data/parser/wiki.png",
+ 'dst' => "$base/e/ea/Thumb.png"
+ ] );
+ $backend->prepare( [ 'dir' => "$base/0/09" ] );
+ $backend->store( [
+ 'src' => "$IP/tests/phpunit/data/parser/headbg.jpg",
+ 'dst' => "$base/0/09/Bad.jpg"
+ ] );
+ $backend->prepare( [ 'dir' => "$base/5/5f" ] );
+ $backend->store( [
+ 'src' => "$IP/tests/phpunit/data/parser/LoremIpsum.djvu",
+ 'dst' => "$base/5/5f/LoremIpsum.djvu"
+ ] );
+
+ // No helpful SVG file to copy, so make one ourselves
+ $data = '<?xml version="1.0" encoding="utf-8"?>' .
+ '<svg xmlns="http://www.w3.org/2000/svg"' .
+ ' version="1.1" width="240" height="180"/>';
+
+ $backend->prepare( [ 'dir' => "$base/f/ff" ] );
+ $backend->quickCreate( [
+ 'content' => $data, 'dst' => "$base/f/ff/Foobar.svg"
+ ] );
+
+ return function () use ( $backend ) {
+ if ( $backend instanceof MockFileBackend ) {
+ // In memory backend, so dont bother cleaning them up.
+ return;
+ }
+ $this->teardownUploadBackend();
+ };
+ }
+
+ /**
+ * Remove the dummy uploads directory
+ */
+ private function teardownUploadBackend() {
+ if ( $this->keepUploads ) {
+ return;
+ }
+
+ $repo = RepoGroup::singleton()->getLocalRepo();
+ $public = $repo->getZonePath( 'public' );
+
+ $this->deleteFiles(
+ [
+ "$public/3/3a/Foobar.jpg",
+ "$public/e/ea/Thumb.png",
+ "$public/0/09/Bad.jpg",
+ "$public/5/5f/LoremIpsum.djvu",
+ "$public/f/ff/Foobar.svg",
+ "$public/0/00/Video.ogv",
+ "$public/4/41/Audio.oga",
+ ]
+ );
+ }
+
+ /**
+ * Delete the specified files and their parent directories
+ * @param array $files File backend URIs mwstore://...
+ */
+ private function deleteFiles( $files ) {
+ // Delete the files
+ $backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
+ foreach ( $files as $file ) {
+ $backend->delete( [ 'src' => $file ], [ 'force' => 1 ] );
+ }
+
+ // Delete the parent directories
+ foreach ( $files as $file ) {
+ $tmp = FileBackend::parentStoragePath( $file );
+ while ( $tmp ) {
+ if ( !$backend->clean( [ 'dir' => $tmp ] )->isOK() ) {
+ break;
+ }
+ $tmp = FileBackend::parentStoragePath( $tmp );
+ }
+ }
+ }
+
+ /**
+ * Add articles to the test DB.
+ *
+ * @param $articles Article info array from TestFileReader
+ */
+ public function addArticles( $articles ) {
+ global $wgContLang;
+ $setup = [];
+ $teardown = [];
+
+ // Be sure ParserTestRunner::addArticle has correct language set,
+ // so that system messages get into the right language cache
+ if ( $wgContLang->getCode() !== 'en' ) {
+ $setup['wgLanguageCode'] = 'en';
+ $setup['wgContLang'] = Language::factory( 'en' );
+ }
+
+ // Add special namespaces, in case that hasn't been done by staticSetup() yet
+ $this->appendNamespaceSetup( $setup, $teardown );
+
+ // wgCapitalLinks obviously needs initialisation
+ $setup['wgCapitalLinks'] = true;
+
+ $teardown[] = $this->executeSetupSnippets( $setup );
+
+ foreach ( $articles as $info ) {
+ $this->addArticle( $info['name'], $info['text'], $info['file'], $info['line'] );
+ }
+
+ // Wipe WANObjectCache process cache, which is invalidated by article insertion
+ // due to T144706
+ ObjectCache::getMainWANInstance()->clearProcessCache();
+
+ $this->executeSetupSnippets( $teardown );
+ }
+
+ /**
+ * Insert a temporary test article
+ * @param string $name The title, including any prefix
+ * @param string $text The article text
+ * @param string $file The input file name
+ * @param int|string $line The input line number, for reporting errors
+ * @throws Exception
+ * @throws MWException
+ */
+ private function addArticle( $name, $text, $file, $line ) {
+ $text = self::chomp( $text );
+ $name = self::chomp( $name );
+
+ $title = Title::newFromText( $name );
+ wfDebug( __METHOD__ . ": adding $name" );
+
+ if ( is_null( $title ) ) {
+ throw new MWException( "invalid title '$name' at $file:$line\n" );
+ }
+
+ $page = WikiPage::factory( $title );
+ $page->loadPageData( 'fromdbmaster' );
+
+ if ( $page->exists() ) {
+ throw new MWException( "duplicate article '$name' at $file:$line\n" );
+ }
+
+ $status = $page->doEditContent( ContentHandler::makeContent( $text, $title ), '', EDIT_NEW );
+ if ( !$status->isOK() ) {
+ throw new MWException( $status->getWikiText( false, false, 'en' ) );
+ }
+
+ // The RepoGroup cache is invalidated by the creation of file redirects
+ if ( $title->getNamespace() === NS_IMAGE ) {
+ RepoGroup::singleton()->clearCache( $title );
+ }
+ }
+
+ /**
+ * Check if a hook is installed
+ *
+ * @param string $name
+ * @return bool True if tag hook is present
+ */
+ public function requireHook( $name ) {
+ global $wgParser;
+
+ $wgParser->firstCallInit(); // make sure hooks are loaded.
+ if ( isset( $wgParser->mTagHooks[$name] ) ) {
+ return true;
+ } else {
+ $this->recorder->warning( " This test suite requires the '$name' hook " .
+ "extension, skipping." );
+ return false;
+ }
+ }
+
+ /**
+ * Check if a function hook is installed
+ *
+ * @param string $name
+ * @return bool True if function hook is present
+ */
+ public function requireFunctionHook( $name ) {
+ global $wgParser;
+
+ $wgParser->firstCallInit(); // make sure hooks are loaded.
+
+ if ( isset( $wgParser->mFunctionHooks[$name] ) ) {
+ return true;
+ } else {
+ $this->recorder->warning( " This test suite requires the '$name' function " .
+ "hook extension, skipping." );
+ return false;
+ }
+ }
+
+ /**
+ * Check if a transparent tag hook is installed
+ *
+ * @param string $name
+ * @return bool True if function hook is present
+ */
+ public function requireTransparentHook( $name ) {
+ global $wgParser;
+
+ $wgParser->firstCallInit(); // make sure hooks are loaded.
+
+ if ( isset( $wgParser->mTransparentTagHooks[$name] ) ) {
+ return true;
+ } else {
+ $this->recorder->warning( " This test suite requires the '$name' transparent " .
+ "hook extension, skipping.\n" );
+ return false;
+ }
+ }
+
+ /**
+ * The ParserGetVariableValueTs hook, used to make sure time-related parser
+ * functions give a persistent value.
+ */
+ static function getFakeTimestamp( &$parser, &$ts ) {
+ $ts = 123; // parsed as '1970-01-01T00:02:03Z'
+ return true;
+ }
+}
--- /dev/null
+<?php
+
+class PhpunitTestRecorder extends TestRecorder {
+ private $testCase;
+
+ public function setTestCase( PHPUnit_Framework_TestCase $testCase ) {
+ $this->testCase = $testCase;
+ }
+
+ /**
+ * Mark a test skipped
+ */
+ public function skipped( $test, $reason ) {
+ $this->testCase->markTestSkipped( "SKIPPED: $reason" );
+ }
+}
-Parser tests are run using our PHPUnit test suite in tests/phpunit:
+Parser tests can be run either via PHPUnit or by using the standalone
+parserTests.php in this directory. The standalone version provides more
+options.
+
+To run parser tests via PHPUnit:
$ cd tests/phpunit
- ./phpunit.php --group Parser
+ ./phpunit.php --testsuite parsertests
-You can optionally filter by title using --regex. I.e. :
+You can optionally filter by title using --filter, e.g.
- ./phpunit.php --group Parser --regex="Bug 6200"
+ ./phpunit.php --testsuite parsertests --filter="Bug 6200"
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Testing
+ */
+
+class TestFileReader {
+ private $file;
+ private $fh;
+ private $section = null;
+ /** String|null: current test section being analyzed */
+ private $sectionData = [];
+ private $lineNum = 0;
+ private $runDisabled;
+ private $runParsoid;
+ private $regex;
+
+ private $articles = [];
+ private $requirements = [];
+ private $tests = [];
+
+ public static function read( $file, array $options = [] ) {
+ $reader = new self( $file, $options );
+ $reader->execute();
+
+ $requirements = [];
+ foreach ( $reader->requirements as $type => $reqsOfType ) {
+ foreach ( $reqsOfType as $name => $unused ) {
+ $requirements[] = [
+ 'type' => $type,
+ 'name' => $name
+ ];
+ }
+ }
+
+ return [
+ 'requirements' => $requirements,
+ 'tests' => $reader->tests,
+ 'articles' => $reader->articles
+ ];
+ }
+
+ private function __construct( $file, $options ) {
+ $this->file = $file;
+ $this->fh = fopen( $this->file, "rt" );
+
+ if ( !$this->fh ) {
+ throw new MWException( "Couldn't open file '$file'\n" );
+ }
+
+ $options = $options + [
+ 'runDisabled' => false,
+ 'runParsoid' => false,
+ 'regex' => '//',
+ ];
+ $this->runDisabled = $options['runDisabled'];
+ $this->runParsoid = $options['runParsoid'];
+ $this->regex = $options['regex'];
+ }
+
+ private function addCurrentTest() {
+ // "input" and "result" are old section names allowed
+ // for backwards-compatibility.
+ $input = $this->checkSection( [ 'wikitext', 'input' ], false );
+ $result = $this->checkSection( [ 'html/php', 'html/*', 'html', 'result' ], false );
+ // Some tests have "with tidy" and "without tidy" variants
+ $tidy = $this->checkSection( [ 'html/php+tidy', 'html+tidy' ], false );
+
+ if ( !isset( $this->sectionData['options'] ) ) {
+ $this->sectionData['options'] = '';
+ }
+
+ if ( !isset( $this->sectionData['config'] ) ) {
+ $this->sectionData['config'] = '';
+ }
+
+ $isDisabled = preg_match( '/\\bdisabled\\b/i', $this->sectionData['options'] ) &&
+ !$this->runDisabled;
+ $isParsoidOnly = preg_match( '/\\bparsoid\\b/i', $this->sectionData['options'] ) &&
+ $result == 'html' &&
+ !$this->runParsoid;
+ $isFiltered = !preg_match( $this->regex, $this->sectionData['test'] );
+ if ( $input == false || $result == false || $isDisabled || $isParsoidOnly || $isFiltered ) {
+ // Disabled test
+ return;
+ }
+
+ $test = [
+ 'test' => ParserTestRunner::chomp( $this->sectionData['test'] ),
+ 'input' => ParserTestRunner::chomp( $this->sectionData[$input] ),
+ 'result' => ParserTestRunner::chomp( $this->sectionData[$result] ),
+ 'options' => ParserTestRunner::chomp( $this->sectionData['options'] ),
+ 'config' => ParserTestRunner::chomp( $this->sectionData['config'] ),
+ ];
+ $test['desc'] = $test['test'];
+ $this->tests[] = $test;
+
+ if ( $tidy !== false ) {
+ $test['options'] .= " tidy";
+ $test['desc'] .= ' (with tidy)';
+ $test['result'] = ParserTestRunner::chomp( $this->sectionData[$tidy] );
+ $this->tests[] = $test;
+ }
+ }
+
+ private function execute() {
+ while ( false !== ( $line = fgets( $this->fh ) ) ) {
+ $this->lineNum++;
+ $matches = [];
+
+ if ( preg_match( '/^!!\s*(\S+)/', $line, $matches ) ) {
+ $this->section = strtolower( $matches[1] );
+
+ if ( $this->section == 'endarticle' ) {
+ $this->checkSection( 'text' );
+ $this->checkSection( 'article' );
+
+ $this->addArticle(
+ ParserTestRunner::chomp( $this->sectionData['article'] ),
+ $this->sectionData['text'], $this->lineNum );
+
+ $this->clearSection();
+
+ continue;
+ }
+
+ if ( $this->section == 'endhooks' ) {
+ $this->checkSection( 'hooks' );
+
+ foreach ( explode( "\n", $this->sectionData['hooks'] ) as $line ) {
+ $line = trim( $line );
+
+ if ( $line ) {
+ $this->addRequirement( 'hook', $line );
+ }
+ }
+
+ $this->clearSection();
+
+ continue;
+ }
+
+ if ( $this->section == 'endfunctionhooks' ) {
+ $this->checkSection( 'functionhooks' );
+
+ foreach ( explode( "\n", $this->sectionData['functionhooks'] ) as $line ) {
+ $line = trim( $line );
+
+ if ( $line ) {
+ $this->addRequirement( 'functionHook', $line );
+ }
+ }
+
+ $this->clearSection();
+
+ continue;
+ }
+
+ if ( $this->section == 'endtransparenthooks' ) {
+ $this->checkSection( 'transparenthooks' );
+
+ foreach ( explode( "\n", $this->sectionData['transparenthooks'] ) as $line ) {
+ $line = trim( $line );
+
+ if ( $line ) {
+ $this->addRequirement( 'transparentHook', $line );
+ }
+ }
+
+ $this->clearSection();
+
+ continue;
+ }
+
+ if ( $this->section == 'end' ) {
+ $this->checkSection( 'test' );
+ $this->addCurrentTest();
+ $this->clearSection();
+ continue;
+ }
+
+ if ( isset( $this->sectionData[$this->section] ) ) {
+ throw new MWException( "duplicate section '$this->section' "
+ . "at line {$this->lineNum} of $this->file\n" );
+ }
+
+ $this->sectionData[$this->section] = '';
+
+ continue;
+ }
+
+ if ( $this->section ) {
+ $this->sectionData[$this->section] .= $line;
+ }
+ }
+ }
+
+ /**
+ * Clear section name and its data
+ */
+ private function clearSection() {
+ $this->sectionData = [];
+ $this->section = null;
+
+ }
+
+ /**
+ * Verify the current section data has some value for the given token
+ * name(s) (first parameter).
+ * Throw an exception if it is not set, referencing current section
+ * and adding the current file name and line number
+ *
+ * @param string|array $tokens Expected token(s) that should have been
+ * mentioned before closing this section
+ * @param bool $fatal True iff an exception should be thrown if
+ * the section is not found.
+ * @return bool|string
+ * @throws MWException
+ */
+ private function checkSection( $tokens, $fatal = true ) {
+ if ( is_null( $this->section ) ) {
+ throw new MWException( __METHOD__ . " can not verify a null section!\n" );
+ }
+ if ( !is_array( $tokens ) ) {
+ $tokens = [ $tokens ];
+ }
+ if ( count( $tokens ) == 0 ) {
+ throw new MWException( __METHOD__ . " can not verify zero sections!\n" );
+ }
+
+ $data = $this->sectionData;
+ $tokens = array_filter( $tokens, function ( $token ) use ( $data ) {
+ return isset( $data[$token] );
+ } );
+
+ if ( count( $tokens ) == 0 ) {
+ if ( !$fatal ) {
+ return false;
+ }
+ throw new MWException( sprintf(
+ "'%s' without '%s' at line %s of %s\n",
+ $this->section,
+ implode( ',', $tokens ),
+ $this->lineNum,
+ $this->file
+ ) );
+ }
+ if ( count( $tokens ) > 1 ) {
+ throw new MWException( sprintf(
+ "'%s' with unexpected tokens '%s' at line %s of %s\n",
+ $this->section,
+ implode( ',', $tokens ),
+ $this->lineNum,
+ $this->file
+ ) );
+ }
+
+ return array_values( $tokens )[0];
+ }
+
+ private function addArticle( $name, $text, $line ) {
+ $this->articles[] = [
+ 'name' => $name,
+ 'text' => $text,
+ 'line' => $line,
+ 'file' => $this->file
+ ];
+ }
+
+ private function addRequirement( $type, $name ) {
+ $this->requirements[$type][$name] = true;
+ }
+}
+
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Testing
+ */
+
+/**
+ * Interface to record parser test results.
+ *
+ * The TestRecorder is an class hierarchy to record the result of
+ * MediaWiki parser tests. One should call start() before running the
+ * full parser tests and end() once all the tests have been finished.
+ * After each test, you should use record() to keep track of your tests
+ * results. Finally, report() is used to generate a summary of your
+ * test run, one could dump it to the console for human consumption or
+ * register the result in a database for tracking purposes.
+ *
+ * @since 1.22
+ */
+abstract class TestRecorder {
+
+ /**
+ * Called at beginning of the parser test run
+ */
+ public function start() {
+ }
+
+ /**
+ * Called before starting a test
+ */
+ public function startTest( $test ) {
+ }
+
+ /**
+ * Called before starting an input file
+ */
+ public function startSuite( $path ) {
+ }
+
+ /**
+ * Called after ending an input file
+ */
+ public function endSuite( $path ) {
+ }
+
+ /**
+ * Called after each test
+ * @param array $test
+ * @param ParserTestResult $result
+ */
+ public function record( $test, ParserTestResult $result ) {
+ }
+
+ /**
+ * Show a warning to the user
+ */
+ public function warning( $message ) {
+ }
+
+ /**
+ * Mark a test skipped
+ */
+ public function skipped( $test, $subtest ) {
+ }
+
+ /**
+ * Called before finishing the test run
+ */
+ public function report() {
+ }
+
+ /**
+ * Called at the end of the parser test run
+ */
+ public function end() {
+ }
+
+}
+
--- /dev/null
+<?php
+
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Testing
+ */
+
+/**
+ * Initialize and detect the tidy support
+ */
+class TidySupport {
+ private $enabled;
+ private $config;
+
+ /**
+ * Determine if there is a usable tidy.
+ */
+ public function __construct( $useConfiguration = false ) {
+ global $IP, $wgUseTidy, $wgTidyBin, $wgTidyInternal, $wgTidyConfig,
+ $wgTidyConf, $wgTidyOpts;
+
+ $this->enabled = true;
+ if ( $useConfiguration ) {
+ if ( $wgTidyConfig !== null ) {
+ $this->config = $wgTidyConfig;
+ } elseif ( $wgUseTidy ) {
+ $this->config = [
+ 'tidyConfigFile' => $wgTidyConf,
+ 'debugComment' => false,
+ 'tidyBin' => $wgTidyBin,
+ 'tidyCommandLine' => $wgTidyOpts
+ ];
+ if ( $wgTidyInternal ) {
+ $this->config['driver'] = wfIsHHVM() ? 'RaggettInternalHHVM' : 'RaggettInternalPHP';
+ } else {
+ $this->config['driver'] = 'RaggettExternal';
+ }
+ } else {
+ $this->enabled = false;
+ }
+ } else {
+ $this->config = [
+ 'tidyConfigFile' => "$IP/includes/tidy/tidy.conf",
+ 'tidyCommandLine' => '',
+ ];
+ if ( extension_loaded( 'tidy' ) && class_exists( 'tidy' ) ) {
+ $this->config['driver'] = wfIsHHVM() ? 'RaggettInternalHHVM' : 'RaggettInternalPHP';
+ } else {
+ if ( is_executable( $wgTidyBin ) ) {
+ $this->config['driver'] = 'RaggettExternal';
+ $this->config['tidyBin'] = $wgTidyBin;
+ } else {
+ $path = Installer::locateExecutableInDefaultPaths( $wgTidyBin );
+ if ( $path !== false ) {
+ $this->config['driver'] = 'RaggettExternal';
+ $this->config['tidyBin'] = $wgTidyBin;
+ } else {
+ $this->enabled = false;
+ }
+ }
+ }
+ }
+ if ( !$this->enabled ) {
+ $this->config = [ 'driver' => 'disabled' ];
+ }
+ }
+
+ /**
+ * Returns true if tidy is usable
+ *
+ * @return bool
+ */
+ public function isEnabled() {
+ return $this->enabled;
+ }
+
+ public function getConfig() {
+ return $this->config;
+ }
+}
--- /dev/null
+<?php
+
+require __DIR__ . '/../../maintenance/Maintenance.php';
+
+// Make RequestContext::resetMain() happy
+define( 'MW_PARSER_TEST', 1 );
+
+class ParserFuzzTest extends Maintenance {
+ private $parserTest;
+ private $maxFuzzTestLength = 300;
+ private $memoryLimit = 100;
+ private $seed;
+
+ function __construct() {
+ parent::__construct();
+ $this->addDescription( 'Run a fuzz test on the parser, until it segfaults ' .
+ 'or throws an exception' );
+ $this->addOption( 'file', 'Use the specified file as a dictionary, ' .
+ ' or leave blank to use parserTests.txt', false, true, true );
+
+ $this->addOption( 'seed', 'Start the fuzz test from the specified seed', false, true );
+ }
+
+ function finalSetup() {
+ self::requireTestsAutoloader();
+ TestSetup::applyInitialConfig();
+ }
+
+ function execute() {
+ $files = $this->getOption( 'file', [ __DIR__ . '/parserTests.txt' ] );
+ $this->seed = intval( $this->getOption( 'seed', 1 ) ) - 1;
+ $this->parserTest = new ParserTestRunner(
+ new MultiTestRecorder,
+ [] );
+ $this->fuzzTest( $files );
+ }
+
+ /**
+ * Run a fuzz test series
+ * Draw input from a set of test files
+ * @param array $filenames
+ */
+ function fuzzTest( $filenames ) {
+ $dict = $this->getFuzzInput( $filenames );
+ $dictSize = strlen( $dict );
+ $logMaxLength = log( $this->maxFuzzTestLength );
+
+ $teardown = $this->parserTest->staticSetup();
+ $teardown = $this->parserTest->setupDatabase( $teardown );
+ $teardown = $this->parserTest->setupUploads( $teardown );
+
+ $fakeTest = [
+ 'test' => '',
+ 'desc' => '',
+ 'input' => '',
+ 'result' => '',
+ 'options' => '',
+ 'config' => ''
+ ];
+
+ ini_set( 'memory_limit', $this->memoryLimit * 1048576 * 2 );
+
+ $numTotal = 0;
+ $numSuccess = 0;
+ $user = new User;
+ $opts = ParserOptions::newFromUser( $user );
+ $title = Title::makeTitle( NS_MAIN, 'Parser_test' );
+
+ while ( true ) {
+ // Generate test input
+ mt_srand( ++$this->seed );
+ $totalLength = mt_rand( 1, $this->maxFuzzTestLength );
+ $input = '';
+
+ while ( strlen( $input ) < $totalLength ) {
+ $logHairLength = mt_rand( 0, 1000000 ) / 1000000 * $logMaxLength;
+ $hairLength = min( intval( exp( $logHairLength ) ), $dictSize );
+ $offset = mt_rand( 0, $dictSize - $hairLength );
+ $input .= substr( $dict, $offset, $hairLength );
+ }
+
+ $perTestTeardown = $this->parserTest->perTestSetup( $fakeTest );
+ $parser = $this->parserTest->getParser();
+
+ // Run the test
+ try {
+ $parser->parse( $input, $title, $opts );
+ $fail = false;
+ } catch ( Exception $exception ) {
+ $fail = true;
+ }
+
+ if ( $fail ) {
+ echo "Test failed with seed {$this->seed}\n";
+ echo "Input:\n";
+ printf( "string(%d) \"%s\"\n\n", strlen( $input ), $input );
+ echo "$exception\n";
+ } else {
+ $numSuccess++;
+ }
+
+ $numTotal++;
+ ScopedCallback::consume( $perTestTeardown );
+
+ if ( $numTotal % 100 == 0 ) {
+ $usage = intval( memory_get_usage( true ) / $this->memoryLimit / 1048576 * 100 );
+ echo "{$this->seed}: $numSuccess/$numTotal (mem: $usage%)\n";
+ if ( $usage >= 100 ) {
+ echo "Out of memory:\n";
+ $memStats = $this->getMemoryBreakdown();
+
+ foreach ( $memStats as $name => $usage ) {
+ echo "$name: $usage\n";
+ }
+ if ( function_exists( 'hphpd_break' ) ) {
+ hphpd_break();
+ }
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Get a memory usage breakdown
+ * @return array
+ */
+ function getMemoryBreakdown() {
+ $memStats = [];
+
+ foreach ( $GLOBALS as $name => $value ) {
+ $memStats['$' . $name] = $this->guessVarSize( $value );
+ }
+
+ $classes = get_declared_classes();
+
+ foreach ( $classes as $class ) {
+ $rc = new ReflectionClass( $class );
+ $props = $rc->getStaticProperties();
+ $memStats[$class] = $this->guessVarSize( $props );
+ $methods = $rc->getMethods();
+
+ foreach ( $methods as $method ) {
+ $memStats[$class] += $this->guessVarSize( $method->getStaticVariables() );
+ }
+ }
+
+ $functions = get_defined_functions();
+
+ foreach ( $functions['user'] as $function ) {
+ $rf = new ReflectionFunction( $function );
+ $memStats["$function()"] = $this->guessVarSize( $rf->getStaticVariables() );
+ }
+
+ asort( $memStats );
+
+ return $memStats;
+ }
+
+ /**
+ * Estimate the size of the input variable
+ */
+ function guessVarSize( $var ) {
+ $length = 0;
+ try {
+ MediaWiki\suppressWarnings();
+ $length = strlen( serialize( $var ) );
+ MediaWiki\restoreWarnings();
+ } catch ( Exception $e ) {
+ }
+ return $length;
+ }
+
+ /**
+ * Get an input dictionary from a set of parser test files
+ * @param array $filenames
+ * @return string
+ */
+ function getFuzzInput( $filenames ) {
+ $dict = '';
+
+ foreach ( $filenames as $filename ) {
+ $contents = file_get_contents( $filename );
+ preg_match_all(
+ '/!!\s*(input|wikitext)\n(.*?)\n!!\s*(result|html|html\/\*|html\/php)/s',
+ $contents,
+ $matches
+ );
+
+ foreach ( $matches[1] as $match ) {
+ $dict .= $match . "\n";
+ }
+ }
+
+ return $dict;
+ }
+}
+
+$maintClass = 'ParserFuzzTest';
+require RUN_MAINTENANCE_IF_MAIN;
+++ /dev/null
-<?php
-/**
- * Helper code for the MediaWiki parser test suite. Some code is duplicated
- * in PHPUnit's NewParserTests.php, so you'll probably want to update both
- * at the same time.
- *
- * Copyright © 2004, 2010 Brion Vibber <brion@pobox.com>
- * https://www.mediawiki.org/
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @todo Make this more independent of the configuration (and if possible the database)
- * @todo document
- * @file
- * @ingroup Testing
- */
-use MediaWiki\MediaWikiServices;
-
-/**
- * @ingroup Testing
- */
-class ParserTest {
- /**
- * @var bool $color whereas output should be colorized
- */
- private $color;
-
- /**
- * @var bool $showOutput Show test output
- */
- private $showOutput;
-
- /**
- * @var bool $useTemporaryTables Use temporary tables for the temporary database
- */
- private $useTemporaryTables = true;
-
- /**
- * @var bool $databaseSetupDone True if the database has been set up
- */
- private $databaseSetupDone = false;
-
- /**
- * Our connection to the database
- * @var DatabaseBase
- */
- private $db;
-
- /**
- * Database clone helper
- * @var CloneDatabase
- */
- private $dbClone;
-
- /**
- * @var DjVuSupport
- */
- private $djVuSupport;
-
- /**
- * @var TidySupport
- */
- private $tidySupport;
-
- /**
- * @var ITestRecorder
- */
- private $recorder;
-
- private $maxFuzzTestLength = 300;
- private $fuzzSeed = 0;
- private $memoryLimit = 50;
- private $uploadDir = null;
-
- public $regex = "";
- private $savedGlobals = [];
- private $useDwdiff = false;
- private $markWhitespace = false;
- private $normalizationFunctions = [];
-
- /**
- * Sets terminal colorization and diff/quick modes depending on OS and
- * command-line options (--color and --quick).
- * @param array $options
- */
- public function __construct( $options = [] ) {
- # Only colorize output if stdout is a terminal.
- $this->color = !wfIsWindows() && Maintenance::posix_isatty( 1 );
-
- if ( isset( $options['color'] ) ) {
- switch ( $options['color'] ) {
- case 'no':
- $this->color = false;
- break;
- case 'yes':
- default:
- $this->color = true;
- break;
- }
- }
-
- $this->term = $this->color
- ? new AnsiTermColorer()
- : new DummyTermColorer();
-
- $this->showDiffs = !isset( $options['quick'] );
- $this->showProgress = !isset( $options['quiet'] );
- $this->showFailure = !(
- isset( $options['quiet'] )
- && ( isset( $options['record'] )
- || isset( $options['compare'] ) ) ); // redundant output
-
- $this->showOutput = isset( $options['show-output'] );
- $this->useDwdiff = isset( $options['dwdiff'] );
- $this->markWhitespace = isset( $options['mark-ws'] );
-
- if ( isset( $options['norm'] ) ) {
- foreach ( explode( ',', $options['norm'] ) as $func ) {
- if ( in_array( $func, [ 'removeTbody', 'trimWhitespace' ] ) ) {
- $this->normalizationFunctions[] = $func;
- } else {
- echo "Warning: unknown normalization option \"$func\"\n";
- }
- }
- }
-
- if ( isset( $options['filter'] ) ) {
- $options['regex'] = $options['filter'];
- }
-
- if ( isset( $options['regex'] ) ) {
- if ( isset( $options['record'] ) ) {
- echo "Warning: --record cannot be used with --regex, disabling --record\n";
- unset( $options['record'] );
- }
- $this->regex = $options['regex'];
- } else {
- # Matches anything
- $this->regex = '';
- }
-
- $this->setupRecorder( $options );
- $this->keepUploads = isset( $options['keep-uploads'] );
-
- if ( $this->keepUploads ) {
- $this->uploadDir = wfTempDir() . '/mwParser-images';
- } else {
- $this->uploadDir = wfTempDir() . "/mwParser-" . mt_rand() . "-images";
- }
-
- if ( isset( $options['seed'] ) ) {
- $this->fuzzSeed = intval( $options['seed'] ) - 1;
- }
-
- $this->runDisabled = isset( $options['run-disabled'] );
- $this->runParsoid = isset( $options['run-parsoid'] );
-
- $this->djVuSupport = new DjVuSupport();
- $this->tidySupport = new TidySupport( isset( $options['use-tidy-config'] ) );
- if ( !$this->tidySupport->isEnabled() ) {
- echo "Warning: tidy is not installed, skipping some tests\n";
- }
-
- $this->hooks = [];
- $this->functionHooks = [];
- $this->transparentHooks = [];
- $this->setUp();
- }
-
- function setUp() {
- global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc,
- $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory,
- $wgExtraNamespaces, $wgNamespaceAliases, $wgNamespaceProtection, $wgLocalFileRepo,
- $wgExtraInterlanguageLinkPrefixes, $wgLocalInterwikis,
- $parserMemc, $wgThumbnailScriptPath, $wgScriptPath, $wgResourceBasePath,
- $wgArticlePath, $wgScript, $wgStylePath, $wgExtensionAssetsPath,
- $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType, $wgLockManagers;
-
- $wgScriptPath = '';
- $wgScript = '/index.php';
- $wgStylePath = '/skins';
- $wgResourceBasePath = '';
- $wgExtensionAssetsPath = '/extensions';
- $wgArticlePath = '/wiki/$1';
- $wgThumbnailScriptPath = false;
- $wgLockManagers = [ [
- 'name' => 'fsLockManager',
- 'class' => 'FSLockManager',
- 'lockDirectory' => $this->uploadDir . '/lockdir',
- ], [
- 'name' => 'nullLockManager',
- 'class' => 'NullLockManager',
- ] ];
- $wgLocalFileRepo = [
- 'class' => 'LocalRepo',
- 'name' => 'local',
- 'url' => 'http://example.com/images',
- 'hashLevels' => 2,
- 'transformVia404' => false,
- 'backend' => new FSFileBackend( [
- 'name' => 'local-backend',
- 'wikiId' => wfWikiID(),
- 'containerPaths' => [
- 'local-public' => $this->uploadDir . '/public',
- 'local-thumb' => $this->uploadDir . '/thumb',
- 'local-temp' => $this->uploadDir . '/temp',
- 'local-deleted' => $this->uploadDir . '/deleted',
- ]
- ] )
- ];
- $wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface';
- $wgNamespaceAliases['Image'] = NS_FILE;
- $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
- # add a namespace shadowing a interwiki link, to test
- # proper precedence when resolving links. (bug 51680)
- $wgExtraNamespaces[100] = 'MemoryAlpha';
- $wgExtraNamespaces[101] = 'MemoryAlpha talk';
-
- // XXX: tests won't run without this (for CACHE_DB)
- if ( $wgMainCacheType === CACHE_DB ) {
- $wgMainCacheType = CACHE_NONE;
- }
- if ( $wgMessageCacheType === CACHE_DB ) {
- $wgMessageCacheType = CACHE_NONE;
- }
- if ( $wgParserCacheType === CACHE_DB ) {
- $wgParserCacheType = CACHE_NONE;
- }
-
- DeferredUpdates::clearPendingUpdates();
- $wgMemc = wfGetMainCache(); // checks $wgMainCacheType
- $messageMemc = wfGetMessageCacheStorage();
- $parserMemc = wfGetParserCacheStorage();
-
- RequestContext::resetMain();
- $context = new RequestContext;
- $wgUser = new User;
- $wgLang = $context->getLanguage();
- $wgOut = $context->getOutput();
- $wgRequest = $context->getRequest();
- $wgParser = new StubObject( 'wgParser', $wgParserConf['class'], [ $wgParserConf ] );
-
- if ( $wgStyleDirectory === false ) {
- $wgStyleDirectory = "$IP/skins";
- }
-
- self::setupInterwikis();
- $wgLocalInterwikis = [ 'local', 'mi' ];
- // "extra language links"
- // see https://gerrit.wikimedia.org/r/111390
- array_push( $wgExtraInterlanguageLinkPrefixes, 'mul' );
-
- // Reset namespace cache
- MWNamespace::getCanonicalNamespaces( true );
- Language::factory( 'en' )->resetNamespaces();
- }
-
- /**
- * Insert hardcoded interwiki in the lookup table.
- *
- * This function insert a set of well known interwikis that are used in
- * the parser tests. They can be considered has fixtures are injected in
- * the interwiki cache by using the 'InterwikiLoadPrefix' hook.
- * Since we are not interested in looking up interwikis in the database,
- * the hook completely replace the existing mechanism (hook returns false).
- */
- public static function setupInterwikis() {
- # Hack: insert a few Wikipedia in-project interwiki prefixes,
- # for testing inter-language links
- Hooks::register( 'InterwikiLoadPrefix', function ( $prefix, &$iwData ) {
- static $testInterwikis = [
- 'local' => [
- 'iw_url' => 'http://doesnt.matter.org/$1',
- 'iw_api' => '',
- 'iw_wikiid' => '',
- 'iw_local' => 0 ],
- 'wikipedia' => [
- 'iw_url' => 'http://en.wikipedia.org/wiki/$1',
- 'iw_api' => '',
- 'iw_wikiid' => '',
- 'iw_local' => 0 ],
- 'meatball' => [
- 'iw_url' => 'http://www.usemod.com/cgi-bin/mb.pl?$1',
- 'iw_api' => '',
- 'iw_wikiid' => '',
- 'iw_local' => 0 ],
- 'memoryalpha' => [
- 'iw_url' => 'http://www.memory-alpha.org/en/index.php/$1',
- 'iw_api' => '',
- 'iw_wikiid' => '',
- 'iw_local' => 0 ],
- 'zh' => [
- 'iw_url' => 'http://zh.wikipedia.org/wiki/$1',
- 'iw_api' => '',
- 'iw_wikiid' => '',
- 'iw_local' => 1 ],
- 'es' => [
- 'iw_url' => 'http://es.wikipedia.org/wiki/$1',
- 'iw_api' => '',
- 'iw_wikiid' => '',
- 'iw_local' => 1 ],
- 'fr' => [
- 'iw_url' => 'http://fr.wikipedia.org/wiki/$1',
- 'iw_api' => '',
- 'iw_wikiid' => '',
- 'iw_local' => 1 ],
- 'ru' => [
- 'iw_url' => 'http://ru.wikipedia.org/wiki/$1',
- 'iw_api' => '',
- 'iw_wikiid' => '',
- 'iw_local' => 1 ],
- 'mi' => [
- 'iw_url' => 'http://mi.wikipedia.org/wiki/$1',
- 'iw_api' => '',
- 'iw_wikiid' => '',
- 'iw_local' => 1 ],
- 'mul' => [
- 'iw_url' => 'http://wikisource.org/wiki/$1',
- 'iw_api' => '',
- 'iw_wikiid' => '',
- 'iw_local' => 1 ],
- ];
- if ( array_key_exists( $prefix, $testInterwikis ) ) {
- $iwData = $testInterwikis[$prefix];
- }
-
- // We only want to rely on the above fixtures
- return false;
- } );// hooks::register
- }
-
- /**
- * Remove the hardcoded interwiki lookup table.
- */
- public static function tearDownInterwikis() {
- Hooks::clear( 'InterwikiLoadPrefix' );
- }
-
- /**
- * Reset the Title-related services that need resetting
- * for each test
- */
- public static function resetTitleServices() {
- $services = MediaWikiServices::getInstance();
- $services->resetServiceForTesting( 'TitleFormatter' );
- $services->resetServiceForTesting( 'TitleParser' );
- $services->resetServiceForTesting( '_MediaWikiTitleCodec' );
- $services->resetServiceForTesting( 'LinkRenderer' );
- $services->resetServiceForTesting( 'LinkRendererFactory' );
- }
-
- public function setupRecorder( $options ) {
- if ( isset( $options['record'] ) ) {
- $this->recorder = new DbTestRecorder( $this );
- $this->recorder->version = isset( $options['setversion'] ) ?
- $options['setversion'] : SpecialVersion::getVersion();
- } elseif ( isset( $options['compare'] ) ) {
- $this->recorder = new DbTestPreviewer( $this );
- } else {
- $this->recorder = new TestRecorder( $this );
- }
- }
-
- /**
- * Remove last character if it is a newline
- * @group utility
- * @param string $s
- * @return string
- */
- public static function chomp( $s ) {
- if ( substr( $s, -1 ) === "\n" ) {
- return substr( $s, 0, -1 );
- } else {
- return $s;
- }
- }
-
- /**
- * Run a fuzz test series
- * Draw input from a set of test files
- * @param array $filenames
- */
- function fuzzTest( $filenames ) {
- $GLOBALS['wgContLang'] = Language::factory( 'en' );
- $dict = $this->getFuzzInput( $filenames );
- $dictSize = strlen( $dict );
- $logMaxLength = log( $this->maxFuzzTestLength );
- $this->setupDatabase();
- ini_set( 'memory_limit', $this->memoryLimit * 1048576 );
-
- $numTotal = 0;
- $numSuccess = 0;
- $user = new User;
- $opts = ParserOptions::newFromUser( $user );
- $title = Title::makeTitle( NS_MAIN, 'Parser_test' );
-
- while ( true ) {
- // Generate test input
- mt_srand( ++$this->fuzzSeed );
- $totalLength = mt_rand( 1, $this->maxFuzzTestLength );
- $input = '';
-
- while ( strlen( $input ) < $totalLength ) {
- $logHairLength = mt_rand( 0, 1000000 ) / 1000000 * $logMaxLength;
- $hairLength = min( intval( exp( $logHairLength ) ), $dictSize );
- $offset = mt_rand( 0, $dictSize - $hairLength );
- $input .= substr( $dict, $offset, $hairLength );
- }
-
- $this->setupGlobals();
- $parser = $this->getParser();
-
- // Run the test
- try {
- $parser->parse( $input, $title, $opts );
- $fail = false;
- } catch ( Exception $exception ) {
- $fail = true;
- }
-
- if ( $fail ) {
- echo "Test failed with seed {$this->fuzzSeed}\n";
- echo "Input:\n";
- printf( "string(%d) \"%s\"\n\n", strlen( $input ), $input );
- echo "$exception\n";
- } else {
- $numSuccess++;
- }
-
- $numTotal++;
- $this->teardownGlobals();
- $parser->__destruct();
-
- if ( $numTotal % 100 == 0 ) {
- $usage = intval( memory_get_usage( true ) / $this->memoryLimit / 1048576 * 100 );
- echo "{$this->fuzzSeed}: $numSuccess/$numTotal (mem: $usage%)\n";
- if ( $usage > 90 ) {
- echo "Out of memory:\n";
- $memStats = $this->getMemoryBreakdown();
-
- foreach ( $memStats as $name => $usage ) {
- echo "$name: $usage\n";
- }
- $this->abort();
- }
- }
- }
- }
-
- /**
- * Get an input dictionary from a set of parser test files
- * @param array $filenames
- * @return string
- */
- function getFuzzInput( $filenames ) {
- $dict = '';
-
- foreach ( $filenames as $filename ) {
- $contents = file_get_contents( $filename );
- preg_match_all(
- '/!!\s*(input|wikitext)\n(.*?)\n!!\s*(result|html|html\/\*|html\/php)/s',
- $contents,
- $matches
- );
-
- foreach ( $matches[1] as $match ) {
- $dict .= $match . "\n";
- }
- }
-
- return $dict;
- }
-
- /**
- * Get a memory usage breakdown
- * @return array
- */
- function getMemoryBreakdown() {
- $memStats = [];
-
- foreach ( $GLOBALS as $name => $value ) {
- $memStats['$' . $name] = strlen( serialize( $value ) );
- }
-
- $classes = get_declared_classes();
-
- foreach ( $classes as $class ) {
- $rc = new ReflectionClass( $class );
- $props = $rc->getStaticProperties();
- $memStats[$class] = strlen( serialize( $props ) );
- $methods = $rc->getMethods();
-
- foreach ( $methods as $method ) {
- $memStats[$class] += strlen( serialize( $method->getStaticVariables() ) );
- }
- }
-
- $functions = get_defined_functions();
-
- foreach ( $functions['user'] as $function ) {
- $rf = new ReflectionFunction( $function );
- $memStats["$function()"] = strlen( serialize( $rf->getStaticVariables() ) );
- }
-
- asort( $memStats );
-
- return $memStats;
- }
-
- function abort() {
- $this->abort();
- }
-
- /**
- * Run a series of tests listed in the given text files.
- * Each test consists of a brief description, wikitext input,
- * and the expected HTML output.
- *
- * Prints status updates on stdout and counts up the total
- * number and percentage of passed tests.
- *
- * @param array $filenames Array of strings
- * @return bool True if passed all tests, false if any tests failed.
- */
- public function runTestsFromFiles( $filenames ) {
- $ok = false;
-
- // be sure, ParserTest::addArticle has correct language set,
- // so that system messages gets into the right language cache
- $GLOBALS['wgLanguageCode'] = 'en';
- $GLOBALS['wgContLang'] = Language::factory( 'en' );
-
- $this->recorder->start();
- try {
- $this->setupDatabase();
- $ok = true;
-
- foreach ( $filenames as $filename ) {
- echo "Running parser tests from: $filename\n";
- $tests = new TestFileIterator( $filename, $this );
- $ok = $this->runTests( $tests ) && $ok;
- }
-
- $this->teardownDatabase();
- $this->recorder->report();
- } catch ( DBError $e ) {
- echo $e->getMessage();
- }
- $this->recorder->end();
-
- return $ok;
- }
-
- function runTests( $tests ) {
- $ok = true;
-
- foreach ( $tests as $t ) {
- $result =
- $this->runTest( $t['test'], $t['input'], $t['result'], $t['options'], $t['config'] );
- $ok = $ok && $result;
- $this->recorder->record( $t['test'], $t['subtest'], $result );
- }
-
- if ( $this->showProgress ) {
- print "\n";
- }
-
- return $ok;
- }
-
- /**
- * Get a Parser object
- *
- * @param string $preprocessor
- * @return Parser
- */
- function getParser( $preprocessor = null ) {
- global $wgParserConf;
-
- $class = $wgParserConf['class'];
- $parser = new $class( [ 'preprocessorClass' => $preprocessor ] + $wgParserConf );
-
- foreach ( $this->hooks as $tag => $callback ) {
- $parser->setHook( $tag, $callback );
- }
-
- foreach ( $this->functionHooks as $tag => $bits ) {
- list( $callback, $flags ) = $bits;
- $parser->setFunctionHook( $tag, $callback, $flags );
- }
-
- foreach ( $this->transparentHooks as $tag => $callback ) {
- $parser->setTransparentTagHook( $tag, $callback );
- }
-
- Hooks::run( 'ParserTestParser', [ &$parser ] );
-
- return $parser;
- }
-
- /**
- * Run a given wikitext input through a freshly-constructed wiki parser,
- * and compare the output against the expected results.
- * Prints status and explanatory messages to stdout.
- *
- * @param string $desc Test's description
- * @param string $input Wikitext to try rendering
- * @param string $result Result to output
- * @param array $opts Test's options
- * @param string $config Overrides for global variables, one per line
- * @return bool
- */
- public function runTest( $desc, $input, $result, $opts, $config ) {
- if ( $this->showProgress ) {
- $this->showTesting( $desc );
- }
-
- $opts = $this->parseOptions( $opts );
- $context = $this->setupGlobals( $opts, $config );
-
- $user = $context->getUser();
- $options = ParserOptions::newFromContext( $context );
-
- if ( isset( $opts['djvu'] ) ) {
- if ( !$this->djVuSupport->isEnabled() ) {
- return $this->showSkipped();
- }
- }
-
- if ( isset( $opts['tidy'] ) ) {
- if ( !$this->tidySupport->isEnabled() ) {
- return $this->showSkipped();
- } else {
- $options->setTidy( true );
- }
- }
-
- if ( isset( $opts['title'] ) ) {
- $titleText = $opts['title'];
- } else {
- $titleText = 'Parser test';
- }
-
- ObjectCache::getMainWANInstance()->clearProcessCache();
- $local = isset( $opts['local'] );
- $preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null;
- $parser = $this->getParser( $preprocessor );
- $title = Title::newFromText( $titleText );
-
- if ( isset( $opts['pst'] ) ) {
- $out = $parser->preSaveTransform( $input, $title, $user, $options );
- } elseif ( isset( $opts['msg'] ) ) {
- $out = $parser->transformMsg( $input, $options, $title );
- } elseif ( isset( $opts['section'] ) ) {
- $section = $opts['section'];
- $out = $parser->getSection( $input, $section );
- } elseif ( isset( $opts['replace'] ) ) {
- $section = $opts['replace'][0];
- $replace = $opts['replace'][1];
- $out = $parser->replaceSection( $input, $section, $replace );
- } elseif ( isset( $opts['comment'] ) ) {
- $out = Linker::formatComment( $input, $title, $local );
- } elseif ( isset( $opts['preload'] ) ) {
- $out = $parser->getPreloadText( $input, $title, $options );
- } else {
- $output = $parser->parse( $input, $title, $options, true, true, 1337 );
- $output->setTOCEnabled( !isset( $opts['notoc'] ) );
- $out = $output->getText();
- if ( isset( $opts['tidy'] ) ) {
- $out = preg_replace( '/\s+$/', '', $out );
- }
-
- if ( isset( $opts['showtitle'] ) ) {
- if ( $output->getTitleText() ) {
- $title = $output->getTitleText();
- }
-
- $out = "$title\n$out";
- }
-
- if ( isset( $opts['showindicators'] ) ) {
- $indicators = '';
- foreach ( $output->getIndicators() as $id => $content ) {
- $indicators .= "$id=$content\n";
- }
- $out = $indicators . $out;
- }
-
- if ( isset( $opts['ill'] ) ) {
- $out = implode( ' ', $output->getLanguageLinks() );
- } elseif ( isset( $opts['cat'] ) ) {
- $outputPage = $context->getOutput();
- $outputPage->addCategoryLinks( $output->getCategories() );
- $cats = $outputPage->getCategoryLinks();
-
- if ( isset( $cats['normal'] ) ) {
- $out = implode( ' ', $cats['normal'] );
- } else {
- $out = '';
- }
- }
- }
-
- $this->teardownGlobals();
-
- if ( count( $this->normalizationFunctions ) ) {
- $result = ParserTestResultNormalizer::normalize( $result, $this->normalizationFunctions );
- $out = ParserTestResultNormalizer::normalize( $out, $this->normalizationFunctions );
- }
-
- $testResult = new ParserTestResult( $desc );
- $testResult->expected = $result;
- $testResult->actual = $out;
-
- return $this->showTestResult( $testResult );
- }
-
- /**
- * Refactored in 1.22 to use ParserTestResult
- * @param ParserTestResult $testResult
- * @return bool
- */
- function showTestResult( ParserTestResult $testResult ) {
- if ( $testResult->isSuccess() ) {
- $this->showSuccess( $testResult );
- return true;
- } else {
- $this->showFailure( $testResult );
- return false;
- }
- }
-
- /**
- * Use a regex to find out the value of an option
- * @param string $key Name of option val to retrieve
- * @param array $opts Options array to look in
- * @param mixed $default Default value returned if not found
- * @return mixed
- */
- private static function getOptionValue( $key, $opts, $default ) {
- $key = strtolower( $key );
-
- if ( isset( $opts[$key] ) ) {
- return $opts[$key];
- } else {
- return $default;
- }
- }
-
- private function parseOptions( $instring ) {
- $opts = [];
- // foo
- // foo=bar
- // foo="bar baz"
- // foo=[[bar baz]]
- // foo=bar,"baz quux"
- // foo={...json...}
- $defs = '(?(DEFINE)
- (?<qstr> # Quoted string
- "
- (?:[^\\\\"] | \\\\.)*
- "
- )
- (?<json>
- \{ # Open bracket
- (?:
- [^"{}] | # Not a quoted string or object, or
- (?&qstr) | # A quoted string, or
- (?&json) # A json object (recursively)
- )*
- \} # Close bracket
- )
- (?<value>
- (?:
- (?&qstr) # Quoted val
- |
- \[\[
- [^]]* # Link target
- \]\]
- |
- [\w-]+ # Plain word
- |
- (?&json) # JSON object
- )
- )
- )';
- $regex = '/' . $defs . '\b
- (?<k>[\w-]+) # Key
- \b
- (?:\s*
- = # First sub-value
- \s*
- (?<v>
- (?&value)
- (?:\s*
- , # Sub-vals 1..N
- \s*
- (?&value)
- )*
- )
- )?
- /x';
- $valueregex = '/' . $defs . '(?&value)/x';
-
- if ( preg_match_all( $regex, $instring, $matches, PREG_SET_ORDER ) ) {
- foreach ( $matches as $bits ) {
- $key = strtolower( $bits['k'] );
- if ( !isset( $bits['v'] ) ) {
- $opts[$key] = true;
- } else {
- preg_match_all( $valueregex, $bits['v'], $vmatches );
- $opts[$key] = array_map( [ $this, 'cleanupOption' ], $vmatches[0] );
- if ( count( $opts[$key] ) == 1 ) {
- $opts[$key] = $opts[$key][0];
- }
- }
- }
- }
- return $opts;
- }
-
- private function cleanupOption( $opt ) {
- if ( substr( $opt, 0, 1 ) == '"' ) {
- return stripcslashes( substr( $opt, 1, -1 ) );
- }
-
- if ( substr( $opt, 0, 2 ) == '[[' ) {
- return substr( $opt, 2, -2 );
- }
-
- if ( substr( $opt, 0, 1 ) == '{' ) {
- return FormatJson::decode( $opt, true );
- }
- return $opt;
- }
-
- /**
- * Set up the global variables for a consistent environment for each test.
- * Ideally this should replace the global configuration entirely.
- * @param string $opts
- * @param string $config
- * @return RequestContext
- */
- private function setupGlobals( $opts = '', $config = '' ) {
- # Find out values for some special options.
- $lang =
- self::getOptionValue( 'language', $opts, 'en' );
- $variant =
- self::getOptionValue( 'variant', $opts, false );
- $maxtoclevel =
- self::getOptionValue( 'wgMaxTocLevel', $opts, 999 );
- $linkHolderBatchSize =
- self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 );
-
- $settings = [
- 'wgServer' => 'http://example.org',
- 'wgServerName' => 'example.org',
- 'wgScript' => '/index.php',
- 'wgScriptPath' => '',
- 'wgArticlePath' => '/wiki/$1',
- 'wgActionPaths' => [],
- 'wgLockManagers' => [ [
- 'name' => 'fsLockManager',
- 'class' => 'FSLockManager',
- 'lockDirectory' => $this->uploadDir . '/lockdir',
- ], [
- 'name' => 'nullLockManager',
- 'class' => 'NullLockManager',
- ] ],
- 'wgLocalFileRepo' => [
- 'class' => 'LocalRepo',
- 'name' => 'local',
- 'url' => 'http://example.com/images',
- 'hashLevels' => 2,
- 'transformVia404' => false,
- 'backend' => new FSFileBackend( [
- 'name' => 'local-backend',
- 'wikiId' => wfWikiID(),
- 'containerPaths' => [
- 'local-public' => $this->uploadDir,
- 'local-thumb' => $this->uploadDir . '/thumb',
- 'local-temp' => $this->uploadDir . '/temp',
- 'local-deleted' => $this->uploadDir . '/delete',
- ]
- ] )
- ],
- 'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ),
- 'wgUploadNavigationUrl' => false,
- 'wgStylePath' => '/skins',
- 'wgSitename' => 'MediaWiki',
- 'wgLanguageCode' => $lang,
- 'wgDBprefix' => $this->db->getType() != 'oracle' ? 'parsertest_' : 'pt_',
- 'wgRawHtml' => self::getOptionValue( 'wgRawHtml', $opts, false ),
- 'wgLang' => null,
- 'wgContLang' => null,
- 'wgNamespacesWithSubpages' => [ 0 => isset( $opts['subpage'] ) ],
- 'wgMaxTocLevel' => $maxtoclevel,
- 'wgCapitalLinks' => true,
- 'wgNoFollowLinks' => true,
- 'wgNoFollowDomainExceptions' => [ 'no-nofollow.org' ],
- 'wgThumbnailScriptPath' => false,
- 'wgUseImageResize' => true,
- 'wgSVGConverter' => 'null',
- 'wgSVGConverters' => [ 'null' => 'echo "1">$output' ],
- 'wgLocaltimezone' => 'UTC',
- 'wgAllowExternalImages' => self::getOptionValue( 'wgAllowExternalImages', $opts, true ),
- 'wgThumbLimits' => [ self::getOptionValue( 'thumbsize', $opts, 180 ) ],
- 'wgDefaultLanguageVariant' => $variant,
- 'wgVariantArticlePath' => false,
- 'wgGroupPermissions' => [ '*' => [
- 'createaccount' => true,
- 'read' => true,
- 'edit' => true,
- 'createpage' => true,
- 'createtalk' => true,
- ] ],
- 'wgNamespaceProtection' => [ NS_MEDIAWIKI => 'editinterface' ],
- 'wgDefaultExternalStore' => [],
- 'wgForeignFileRepos' => [],
- 'wgLinkHolderBatchSize' => $linkHolderBatchSize,
- 'wgExperimentalHtmlIds' => false,
- 'wgExternalLinkTarget' => false,
- 'wgHtml5' => true,
- 'wgAdaptiveMessageCache' => true,
- 'wgDisableLangConversion' => false,
- 'wgDisableTitleConversion' => false,
- // Tidy options.
- 'wgUseTidy' => false,
- 'wgTidyConfig' => isset( $opts['tidy'] ) ? $this->tidySupport->getConfig() : null
- ];
-
- if ( $config ) {
- $configLines = explode( "\n", $config );
-
- foreach ( $configLines as $line ) {
- list( $var, $value ) = explode( '=', $line, 2 );
-
- $settings[$var] = eval( "return $value;" );
- }
- }
-
- $this->savedGlobals = [];
-
- /** @since 1.20 */
- Hooks::run( 'ParserTestGlobals', [ &$settings ] );
-
- foreach ( $settings as $var => $val ) {
- if ( array_key_exists( $var, $GLOBALS ) ) {
- $this->savedGlobals[$var] = $GLOBALS[$var];
- }
-
- $GLOBALS[$var] = $val;
- }
-
- // Must be set before $context as user language defaults to $wgContLang
- $GLOBALS['wgContLang'] = Language::factory( $lang );
- $GLOBALS['wgMemc'] = new EmptyBagOStuff;
-
- RequestContext::resetMain();
- $context = RequestContext::getMain();
- $GLOBALS['wgLang'] = $context->getLanguage();
- $GLOBALS['wgOut'] = $context->getOutput();
- $GLOBALS['wgUser'] = $context->getUser();
-
- // We (re)set $wgThumbLimits to a single-element array above.
- $context->getUser()->setOption( 'thumbsize', 0 );
-
- global $wgHooks;
-
- $wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup';
- $wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp';
-
- MagicWord::clearCache();
- MWTidy::destroySingleton();
- RepoGroup::destroySingleton();
-
- self::resetTitleServices();
-
- return $context;
- }
-
- /**
- * List of temporary tables to create, without prefix.
- * Some of these probably aren't necessary.
- * @return array
- */
- private function listTables() {
- $tables = [ 'user', 'user_properties', 'user_former_groups', 'page', 'page_restrictions',
- 'protected_titles', 'revision', 'text', 'pagelinks', 'imagelinks',
- 'categorylinks', 'templatelinks', 'externallinks', 'langlinks', 'iwlinks',
- 'site_stats', 'ipblocks', 'image', 'oldimage',
- 'recentchanges', 'watchlist', 'interwiki', 'logging', 'log_search',
- 'querycache', 'objectcache', 'job', 'l10n_cache', 'redirect', 'querycachetwo',
- 'archive', 'user_groups', 'page_props', 'category'
- ];
-
- if ( in_array( $this->db->getType(), [ 'mysql', 'sqlite', 'oracle' ] ) ) {
- array_push( $tables, 'searchindex' );
- }
-
- // Allow extensions to add to the list of tables to duplicate;
- // may be necessary if they hook into page save or other code
- // which will require them while running tests.
- Hooks::run( 'ParserTestTables', [ &$tables ] );
-
- return $tables;
- }
-
- /**
- * Set up a temporary set of wiki tables to work with for the tests.
- * Currently this will only be done once per run, and any changes to
- * the db will be visible to later tests in the run.
- */
- public function setupDatabase() {
- global $wgDBprefix;
-
- if ( $this->databaseSetupDone ) {
- return;
- }
-
- $this->db = wfGetDB( DB_MASTER );
- $dbType = $this->db->getType();
-
- if ( $wgDBprefix === 'parsertest_' || ( $dbType == 'oracle' && $wgDBprefix === 'pt_' ) ) {
- throw new MWException( 'setupDatabase should be called before setupGlobals' );
- }
-
- $this->databaseSetupDone = true;
-
- # SqlBagOStuff broke when using temporary tables on r40209 (bug 15892).
- # It seems to have been fixed since (r55079?), but regressed at some point before r85701.
- # This works around it for now...
- ObjectCache::$instances[CACHE_DB] = new HashBagOStuff;
-
- # CREATE TEMPORARY TABLE breaks if there is more than one server
- if ( wfGetLB()->getServerCount() != 1 ) {
- $this->useTemporaryTables = false;
- }
-
- $temporary = $this->useTemporaryTables || $dbType == 'postgres';
- $prefix = $dbType != 'oracle' ? 'parsertest_' : 'pt_';
-
- $this->dbClone = new CloneDatabase( $this->db, $this->listTables(), $prefix );
- $this->dbClone->useTemporaryTables( $temporary );
- $this->dbClone->cloneTableStructure();
-
- if ( $dbType == 'oracle' ) {
- $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
- # Insert 0 user to prevent FK violations
-
- # Anonymous user
- $this->db->insert( 'user', [
- 'user_id' => 0,
- 'user_name' => 'Anonymous' ] );
- }
-
- # Update certain things in site_stats
- $this->db->insert( 'site_stats',
- [ 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ] );
-
- # Reinitialise the LocalisationCache to match the database state
- Language::getLocalisationCache()->unloadAll();
-
- # Clear the message cache
- MessageCache::singleton()->clear();
-
- // Remember to update newParserTests.php after changing the below
- // (and it uses a slightly different syntax just for teh lulz)
- $this->setupUploadDir();
- $user = User::createNew( 'WikiSysop' );
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) );
- # note that the size/width/height/bits/etc of the file
- # are actually set by inspecting the file itself; the arguments
- # to recordUpload2 have no effect. That said, we try to make things
- # match up so it is less confusing to readers of the code & tests.
- $image->recordUpload2( '', 'Upload of some lame file', 'Some lame file', [
- 'size' => 7881,
- 'width' => 1941,
- 'height' => 220,
- 'bits' => 8,
- 'media_type' => MEDIATYPE_BITMAP,
- 'mime' => 'image/jpeg',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '1', 16, 36, 31 ),
- 'fileExists' => true
- ], $this->db->timestamp( '20010115123500' ), $user );
-
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Thumb.png' ) );
- # again, note that size/width/height below are ignored; see above.
- $image->recordUpload2( '', 'Upload of some lame thumbnail', 'Some lame thumbnail', [
- 'size' => 22589,
- 'width' => 135,
- 'height' => 135,
- 'bits' => 8,
- 'media_type' => MEDIATYPE_BITMAP,
- 'mime' => 'image/png',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '2', 16, 36, 31 ),
- 'fileExists' => true
- ], $this->db->timestamp( '20130225203040' ), $user );
-
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.svg' ) );
- $image->recordUpload2( '', 'Upload of some lame SVG', 'Some lame SVG', [
- 'size' => 12345,
- 'width' => 240,
- 'height' => 180,
- 'bits' => 0,
- 'media_type' => MEDIATYPE_DRAWING,
- 'mime' => 'image/svg+xml',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
- 'fileExists' => true
- ], $this->db->timestamp( '20010115123500' ), $user );
-
- # This image will be blacklisted in [[MediaWiki:Bad image list]]
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) );
- $image->recordUpload2( '', 'zomgnotcensored', 'Borderline image', [
- 'size' => 12345,
- 'width' => 320,
- 'height' => 240,
- 'bits' => 24,
- 'media_type' => MEDIATYPE_BITMAP,
- 'mime' => 'image/jpeg',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '3', 16, 36, 31 ),
- 'fileExists' => true
- ], $this->db->timestamp( '20010115123500' ), $user );
-
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Video.ogv' ) );
- $image->recordUpload2( '', 'A pretty movie', 'Will it play', [
- 'size' => 12345,
- 'width' => 320,
- 'height' => 240,
- 'bits' => 0,
- 'media_type' => MEDIATYPE_VIDEO,
- 'mime' => 'application/ogg',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
- 'fileExists' => true
- ], $this->db->timestamp( '20010115123500' ), $user );
-
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Audio.oga' ) );
- $image->recordUpload2( '', 'An awesome hitsong', 'Will it play', [
- 'size' => 12345,
- 'width' => 0,
- 'height' => 0,
- 'bits' => 0,
- 'media_type' => MEDIATYPE_AUDIO,
- 'mime' => 'application/ogg',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
- 'fileExists' => true
- ], $this->db->timestamp( '20010115123500' ), $user );
-
- # A DjVu file
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'LoremIpsum.djvu' ) );
- $image->recordUpload2( '', 'Upload a DjVu', 'A DjVu', [
- 'size' => 3249,
- 'width' => 2480,
- 'height' => 3508,
- 'bits' => 0,
- 'media_type' => MEDIATYPE_BITMAP,
- 'mime' => 'image/vnd.djvu',
- 'metadata' => '<?xml version="1.0" ?>
-<!DOCTYPE DjVuXML PUBLIC "-//W3C//DTD DjVuXML 1.1//EN" "pubtext/DjVuXML-s.dtd">
-<DjVuXML>
-<HEAD></HEAD>
-<BODY><OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-</BODY>
-</DjVuXML>',
- 'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
- 'fileExists' => true
- ], $this->db->timestamp( '20010115123600' ), $user );
- }
-
- public function teardownDatabase() {
- if ( !$this->databaseSetupDone ) {
- $this->teardownGlobals();
- return;
- }
- $this->teardownUploadDir( $this->uploadDir );
-
- $this->dbClone->destroy();
- $this->databaseSetupDone = false;
-
- if ( $this->useTemporaryTables ) {
- if ( $this->db->getType() == 'sqlite' ) {
- # Under SQLite the searchindex table is virtual and need
- # to be explicitly destroyed. See bug 29912
- # See also MediaWikiTestCase::destroyDB()
- wfDebug( __METHOD__ . " explicitly destroying sqlite virtual table parsertest_searchindex\n" );
- $this->db->query( "DROP TABLE `parsertest_searchindex`" );
- }
- # Don't need to do anything
- $this->teardownGlobals();
- return;
- }
-
- $tables = $this->listTables();
-
- foreach ( $tables as $table ) {
- if ( $this->db->getType() == 'oracle' ) {
- $this->db->query( "DROP TABLE pt_$table DROP CONSTRAINTS" );
- } else {
- $this->db->query( "DROP TABLE `parsertest_$table`" );
- }
- }
-
- if ( $this->db->getType() == 'oracle' ) {
- $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
- }
-
- $this->teardownGlobals();
- }
-
- /**
- * Create a dummy uploads directory which will contain a couple
- * of files in order to pass existence tests.
- *
- * @return string The directory
- */
- private function setupUploadDir() {
- global $IP;
-
- $dir = $this->uploadDir;
- if ( $this->keepUploads && is_dir( $dir ) ) {
- return;
- }
-
- // wfDebug( "Creating upload directory $dir\n" );
- if ( file_exists( $dir ) ) {
- wfDebug( "Already exists!\n" );
- return;
- }
-
- wfMkdirParents( $dir . '/3/3a', null, __METHOD__ );
- copy( "$IP/tests/phpunit/data/parser/headbg.jpg", "$dir/3/3a/Foobar.jpg" );
- wfMkdirParents( $dir . '/e/ea', null, __METHOD__ );
- copy( "$IP/tests/phpunit/data/parser/wiki.png", "$dir/e/ea/Thumb.png" );
- wfMkdirParents( $dir . '/0/09', null, __METHOD__ );
- copy( "$IP/tests/phpunit/data/parser/headbg.jpg", "$dir/0/09/Bad.jpg" );
- wfMkdirParents( $dir . '/f/ff', null, __METHOD__ );
- file_put_contents( "$dir/f/ff/Foobar.svg",
- '<?xml version="1.0" encoding="utf-8"?>' .
- '<svg xmlns="http://www.w3.org/2000/svg"' .
- ' version="1.1" width="240" height="180"/>' );
- wfMkdirParents( $dir . '/5/5f', null, __METHOD__ );
- copy( "$IP/tests/phpunit/data/parser/LoremIpsum.djvu", "$dir/5/5f/LoremIpsum.djvu" );
- wfMkdirParents( $dir . '/0/00', null, __METHOD__ );
- copy( "$IP/tests/phpunit/data/parser/320x240.ogv", "$dir/0/00/Video.ogv" );
- wfMkdirParents( $dir . '/4/41', null, __METHOD__ );
- copy( "$IP/tests/phpunit/data/media/say-test.ogg", "$dir/4/41/Audio.oga" );
-
- return;
- }
-
- /**
- * Restore default values and perform any necessary clean-up
- * after each test runs.
- */
- private function teardownGlobals() {
- RepoGroup::destroySingleton();
- FileBackendGroup::destroySingleton();
- LockManagerGroup::destroySingletons();
- LinkCache::singleton()->clear();
- MWTidy::destroySingleton();
-
- foreach ( $this->savedGlobals as $var => $val ) {
- $GLOBALS[$var] = $val;
- }
- }
-
- /**
- * Remove the dummy uploads directory
- * @param string $dir
- */
- private function teardownUploadDir( $dir ) {
- if ( $this->keepUploads ) {
- return;
- }
-
- // delete the files first, then the dirs.
- self::deleteFiles(
- [
- "$dir/3/3a/Foobar.jpg",
- "$dir/thumb/3/3a/Foobar.jpg/*.jpg",
- "$dir/e/ea/Thumb.png",
- "$dir/0/09/Bad.jpg",
- "$dir/5/5f/LoremIpsum.djvu",
- "$dir/thumb/5/5f/LoremIpsum.djvu/*-LoremIpsum.djvu.jpg",
- "$dir/f/ff/Foobar.svg",
- "$dir/thumb/f/ff/Foobar.svg/*-Foobar.svg.png",
- "$dir/math/f/a/5/fa50b8b616463173474302ca3e63586b.png",
- "$dir/0/00/Video.ogv",
- "$dir/thumb/0/00/Video.ogv/120px--Video.ogv.jpg",
- "$dir/thumb/0/00/Video.ogv/180px--Video.ogv.jpg",
- "$dir/thumb/0/00/Video.ogv/240px--Video.ogv.jpg",
- "$dir/thumb/0/00/Video.ogv/320px--Video.ogv.jpg",
- "$dir/thumb/0/00/Video.ogv/270px--Video.ogv.jpg",
- "$dir/thumb/0/00/Video.ogv/320px-seek=2-Video.ogv.jpg",
- "$dir/thumb/0/00/Video.ogv/320px-seek=3.3666666666667-Video.ogv.jpg",
- "$dir/4/41/Audio.oga",
- ]
- );
-
- self::deleteDirs(
- [
- "$dir/3/3a",
- "$dir/3",
- "$dir/thumb/3/3a/Foobar.jpg",
- "$dir/thumb/3/3a",
- "$dir/thumb/3",
- "$dir/e/ea",
- "$dir/e",
- "$dir/f/ff/",
- "$dir/f/",
- "$dir/thumb/f/ff/Foobar.svg",
- "$dir/thumb/f/ff/",
- "$dir/thumb/f/",
- "$dir/0/00/",
- "$dir/0/09/",
- "$dir/0/",
- "$dir/5/5f",
- "$dir/5",
- "$dir/thumb/0/00/Video.ogv",
- "$dir/thumb/0/00",
- "$dir/thumb/0",
- "$dir/thumb/5/5f/LoremIpsum.djvu",
- "$dir/thumb/5/5f",
- "$dir/thumb/5",
- "$dir/thumb",
- "$dir/4/41",
- "$dir/4",
- "$dir/math/f/a/5",
- "$dir/math/f/a",
- "$dir/math/f",
- "$dir/math",
- "$dir/lockdir",
- "$dir",
- ]
- );
- }
-
- /**
- * Delete the specified files, if they exist.
- * @param array $files Full paths to files to delete.
- */
- private static function deleteFiles( $files ) {
- foreach ( $files as $pattern ) {
- foreach ( glob( $pattern ) as $file ) {
- if ( file_exists( $file ) ) {
- unlink( $file );
- }
- }
- }
- }
-
- /**
- * Delete the specified directories, if they exist. Must be empty.
- * @param array $dirs Full paths to directories to delete.
- */
- private static function deleteDirs( $dirs ) {
- foreach ( $dirs as $dir ) {
- if ( is_dir( $dir ) ) {
- rmdir( $dir );
- }
- }
- }
-
- /**
- * "Running test $desc..."
- * @param string $desc
- */
- protected function showTesting( $desc ) {
- print "Running test $desc... ";
- }
-
- /**
- * Print a happy success message.
- *
- * Refactored in 1.22 to use ParserTestResult
- *
- * @param ParserTestResult $testResult
- * @return bool
- */
- protected function showSuccess( ParserTestResult $testResult ) {
- if ( $this->showProgress ) {
- print $this->term->color( '1;32' ) . 'PASSED' . $this->term->reset() . "\n";
- }
-
- return true;
- }
-
- /**
- * Print a failure message and provide some explanatory output
- * about what went wrong if so configured.
- *
- * Refactored in 1.22 to use ParserTestResult
- *
- * @param ParserTestResult $testResult
- * @return bool
- */
- protected function showFailure( ParserTestResult $testResult ) {
- if ( $this->showFailure ) {
- if ( !$this->showProgress ) {
- # In quiet mode we didn't show the 'Testing' message before the
- # test, in case it succeeded. Show it now:
- $this->showTesting( $testResult->description );
- }
-
- print $this->term->color( '31' ) . 'FAILED!' . $this->term->reset() . "\n";
-
- if ( $this->showOutput ) {
- print "--- Expected ---\n{$testResult->expected}\n";
- print "--- Actual ---\n{$testResult->actual}\n";
- }
-
- if ( $this->showDiffs ) {
- print $this->quickDiff( $testResult->expected, $testResult->actual );
- if ( !$this->wellFormed( $testResult->actual ) ) {
- print "XML error: $this->mXmlError\n";
- }
- }
- }
-
- return false;
- }
-
- /**
- * Print a skipped message.
- *
- * @return bool
- */
- protected function showSkipped() {
- if ( $this->showProgress ) {
- print $this->term->color( '1;33' ) . 'SKIPPED' . $this->term->reset() . "\n";
- }
-
- return true;
- }
-
- /**
- * Run given strings through a diff and return the (colorized) output.
- * Requires writable /tmp directory and a 'diff' command in the PATH.
- *
- * @param string $input
- * @param string $output
- * @param string $inFileTail Tailing for the input file name
- * @param string $outFileTail Tailing for the output file name
- * @return string
- */
- protected function quickDiff( $input, $output,
- $inFileTail = 'expected', $outFileTail = 'actual'
- ) {
- if ( $this->markWhitespace ) {
- $pairs = [
- "\n" => '¶',
- ' ' => '·',
- "\t" => '→'
- ];
- $input = strtr( $input, $pairs );
- $output = strtr( $output, $pairs );
- }
-
- # Windows, or at least the fc utility, is retarded
- $slash = wfIsWindows() ? '\\' : '/';
- $prefix = wfTempDir() . "{$slash}mwParser-" . mt_rand();
-
- $infile = "$prefix-$inFileTail";
- $this->dumpToFile( $input, $infile );
-
- $outfile = "$prefix-$outFileTail";
- $this->dumpToFile( $output, $outfile );
-
- $shellInfile = wfEscapeShellArg( $infile );
- $shellOutfile = wfEscapeShellArg( $outfile );
-
- global $wgDiff3;
- // we assume that people with diff3 also have usual diff
- if ( $this->useDwdiff ) {
- $shellCommand = 'dwdiff -Pc';
- } else {
- $shellCommand = ( wfIsWindows() && !$wgDiff3 ) ? 'fc' : 'diff -au';
- }
-
- $diff = wfShellExec( "$shellCommand $shellInfile $shellOutfile" );
-
- unlink( $infile );
- unlink( $outfile );
-
- if ( $this->useDwdiff ) {
- return $diff;
- } else {
- return $this->colorDiff( $diff );
- }
- }
-
- /**
- * Write the given string to a file, adding a final newline.
- *
- * @param string $data
- * @param string $filename
- */
- private function dumpToFile( $data, $filename ) {
- $file = fopen( $filename, "wt" );
- fwrite( $file, $data . "\n" );
- fclose( $file );
- }
-
- /**
- * Colorize unified diff output if set for ANSI color output.
- * Subtractions are colored blue, additions red.
- *
- * @param string $text
- * @return string
- */
- protected function colorDiff( $text ) {
- return preg_replace(
- [ '/^(-.*)$/m', '/^(\+.*)$/m' ],
- [ $this->term->color( 34 ) . '$1' . $this->term->reset(),
- $this->term->color( 31 ) . '$1' . $this->term->reset() ],
- $text );
- }
-
- /**
- * Show "Reading tests from ..."
- *
- * @param string $path
- */
- public function showRunFile( $path ) {
- print $this->term->color( 1 ) .
- "Reading tests from \"$path\"..." .
- $this->term->reset() .
- "\n";
- }
-
- /**
- * Insert a temporary test article
- * @param string $name The title, including any prefix
- * @param string $text The article text
- * @param int|string $line The input line number, for reporting errors
- * @param bool|string $ignoreDuplicate Whether to silently ignore duplicate pages
- * @throws Exception
- * @throws MWException
- */
- public static function addArticle( $name, $text, $line = 'unknown', $ignoreDuplicate = '' ) {
- global $wgCapitalLinks;
-
- $oldCapitalLinks = $wgCapitalLinks;
- $wgCapitalLinks = true; // We only need this from SetupGlobals() See r70917#c8637
-
- $text = self::chomp( $text );
- $name = self::chomp( $name );
-
- $title = Title::newFromText( $name );
-
- if ( is_null( $title ) ) {
- throw new MWException( "invalid title '$name' at line $line\n" );
- }
-
- $page = WikiPage::factory( $title );
- $page->loadPageData( 'fromdbmaster' );
-
- if ( $page->exists() ) {
- if ( $ignoreDuplicate == 'ignoreduplicate' ) {
- return;
- } else {
- throw new MWException( "duplicate article '$name' at line $line\n" );
- }
- }
-
- $page->doEditContent( ContentHandler::makeContent( $text, $title ), '', EDIT_NEW );
-
- $wgCapitalLinks = $oldCapitalLinks;
- }
-
- /**
- * Steal a callback function from the primary parser, save it for
- * application to our scary parser. If the hook is not installed,
- * abort processing of this file.
- *
- * @param string $name
- * @return bool True if tag hook is present
- */
- public function requireHook( $name ) {
- global $wgParser;
-
- $wgParser->firstCallInit(); // make sure hooks are loaded.
-
- if ( isset( $wgParser->mTagHooks[$name] ) ) {
- $this->hooks[$name] = $wgParser->mTagHooks[$name];
- } else {
- echo " This test suite requires the '$name' hook extension, skipping.\n";
- return false;
- }
-
- return true;
- }
-
- /**
- * Steal a callback function from the primary parser, save it for
- * application to our scary parser. If the hook is not installed,
- * abort processing of this file.
- *
- * @param string $name
- * @return bool True if function hook is present
- */
- public function requireFunctionHook( $name ) {
- global $wgParser;
-
- $wgParser->firstCallInit(); // make sure hooks are loaded.
-
- if ( isset( $wgParser->mFunctionHooks[$name] ) ) {
- $this->functionHooks[$name] = $wgParser->mFunctionHooks[$name];
- } else {
- echo " This test suite requires the '$name' function hook extension, skipping.\n";
- return false;
- }
-
- return true;
- }
-
- /**
- * Steal a callback function from the primary parser, save it for
- * application to our scary parser. If the hook is not installed,
- * abort processing of this file.
- *
- * @param string $name
- * @return bool True if function hook is present
- */
- public function requireTransparentHook( $name ) {
- global $wgParser;
-
- $wgParser->firstCallInit(); // make sure hooks are loaded.
-
- if ( isset( $wgParser->mTransparentTagHooks[$name] ) ) {
- $this->transparentHooks[$name] = $wgParser->mTransparentTagHooks[$name];
- } else {
- echo " This test suite requires the '$name' transparent hook extension, skipping.\n";
- return false;
- }
-
- return true;
- }
-
- private function wellFormed( $text ) {
- $html =
- Sanitizer::hackDocType() .
- '<html>' .
- $text .
- '</html>';
-
- $parser = xml_parser_create( "UTF-8" );
-
- # case folding violates XML standard, turn it off
- xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
-
- if ( !xml_parse( $parser, $html, true ) ) {
- $err = xml_error_string( xml_get_error_code( $parser ) );
- $position = xml_get_current_byte_index( $parser );
- $fragment = $this->extractFragment( $html, $position );
- $this->mXmlError = "$err at byte $position:\n$fragment";
- xml_parser_free( $parser );
-
- return false;
- }
-
- xml_parser_free( $parser );
-
- return true;
- }
-
- private function extractFragment( $text, $position ) {
- $start = max( 0, $position - 10 );
- $before = $position - $start;
- $fragment = '...' .
- $this->term->color( 34 ) .
- substr( $text, $start, $before ) .
- $this->term->color( 0 ) .
- $this->term->color( 31 ) .
- $this->term->color( 1 ) .
- substr( $text, $position, 1 ) .
- $this->term->color( 0 ) .
- $this->term->color( 34 ) .
- substr( $text, $position + 1, 9 ) .
- $this->term->color( 0 ) .
- '...';
- $display = str_replace( "\n", ' ', $fragment );
- $caret = ' ' .
- str_repeat( ' ', $before ) .
- $this->term->color( 31 ) .
- '^' .
- $this->term->color( 0 );
-
- return "$display\n$caret";
- }
-
- static function getFakeTimestamp( &$parser, &$ts ) {
- $ts = 123; // parsed as '1970-01-01T00:02:03Z'
- return true;
- }
-}
-
-class ParserTestResultNormalizer {
- protected $doc, $xpath, $invalid;
-
- public static function normalize( $text, $funcs ) {
- $norm = new self( $text );
- if ( $norm->invalid ) {
- return $text;
- }
- foreach ( $funcs as $func ) {
- $norm->$func();
- }
- return $norm->serialize();
- }
-
- protected function __construct( $text ) {
- $this->doc = new DOMDocument( '1.0', 'utf-8' );
-
- // Note: parsing a supposedly XHTML document with an XML parser is not
- // guaranteed to give accurate results. For example, it may introduce
- // differences in the number of line breaks in <pre> tags.
-
- MediaWiki\suppressWarnings();
- if ( !$this->doc->loadXML( '<html><body>' . $text . '</body></html>' ) ) {
- $this->invalid = true;
- }
- MediaWiki\restoreWarnings();
- $this->xpath = new DOMXPath( $this->doc );
- $this->body = $this->xpath->query( '//body' )->item( 0 );
- }
-
- protected function removeTbody() {
- foreach ( $this->xpath->query( '//tbody' ) as $tbody ) {
- while ( $tbody->firstChild ) {
- $child = $tbody->firstChild;
- $tbody->removeChild( $child );
- $tbody->parentNode->insertBefore( $child, $tbody );
- }
- $tbody->parentNode->removeChild( $tbody );
- }
- }
-
- /**
- * The point of this function is to produce a normalized DOM in which
- * Tidy's output matches the output of html5depurate. Tidy both trims
- * and pretty-prints, so this requires fairly aggressive treatment.
- *
- * In particular, note that Tidy converts <pre>x</pre> to <pre>\nx\n</pre>,
- * which theoretically affects display since the second line break is not
- * ignored by compliant HTML parsers.
- *
- * This function also removes empty elements, as does Tidy.
- */
- protected function trimWhitespace() {
- foreach ( $this->xpath->query( '//text()' ) as $child ) {
- if ( strtolower( $child->parentNode->nodeName ) === 'pre' ) {
- // Just trim one line break from the start and end
- if ( substr_compare( $child->data, "\n", 0 ) === 0 ) {
- $child->data = substr( $child->data, 1 );
- }
- if ( substr_compare( $child->data, "\n", -1 ) === 0 ) {
- $child->data = substr( $child->data, 0, -1 );
- }
- } else {
- // Trim all whitespace
- $child->data = trim( $child->data );
- }
- if ( $child->data === '' ) {
- $child->parentNode->removeChild( $child );
- }
- }
- }
-
- /**
- * Serialize the XML DOM for comparison purposes. This does not generate HTML.
- */
- protected function serialize() {
- return strtr( $this->doc->saveXML( $this->body ),
- [ '<body>' => '', '</body>' => '' ] );
- }
-}
--- /dev/null
+<?php
+/**
+ * MediaWiki parser test suite
+ *
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>
+ * https://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Testing
+ */
+
+// Some methods which are discouraged for normal code throw exceptions unless
+// we declare this is just a test.
+define( 'MW_PARSER_TEST', true );
+
+require __DIR__ . '/../../maintenance/Maintenance.php';
+
+class ParserTestsMaintenance extends Maintenance {
+ function __construct() {
+ parent::__construct();
+ $this->addDescription( 'Run parser tests' );
+
+ $this->addOption( 'quick', 'Suppress diff output of failed tests' );
+ $this->addOption( 'quiet', 'Suppress notification of passed tests (shows only failed tests)' );
+ $this->addOption( 'show-output', 'Show expected and actual output' );
+ $this->addOption( 'color', '[=yes|no] Override terminal detection and force ' .
+ 'color output on or off. Use wgCommandLineDarkBg = true; if your term is dark',
+ false, true );
+ $this->addOption( 'regex', 'Only run tests whose descriptions which match given regex',
+ false, true );
+ $this->addOption( 'filter', 'Alias for --regex', false, true );
+ $this->addOption( 'file', 'Run test cases from a custom file instead of parserTests.txt',
+ false, true, false, true );
+ $this->addOption( 'record', 'Record tests in database' );
+ $this->addOption( 'compare', 'Compare with recorded results, without updating the database.' );
+ $this->addOption( 'setversion', 'When using --record, set the version string to use (useful' .
+ 'with "git rev-parse HEAD" to get the exact revision)',
+ false, true );
+ $this->addOption( 'keep-uploads', 'Re-use the same upload directory for each ' .
+ 'test, don\'t delete it' );
+ $this->addOption( 'file-backend', 'Use the file backend with the given name,' .
+ 'and upload files to it, instead of creating a mock file backend.', false, true );
+ $this->addOption( 'upload-dir', 'Specify the upload directory to use. Useful in ' .
+ 'conjunction with --keep-uploads. Causes a real (non-mock) file backend to ' .
+ 'be used.', false, true );
+ $this->addOption( 'run-disabled', 'run disabled tests' );
+ $this->addOption( 'run-parsoid', 'run parsoid tests (normally disabled)' );
+ $this->addOption( 'dwdiff', 'Use dwdiff to display diff output' );
+ $this->addOption( 'mark-ws', 'Mark whitespace in diffs by replacing it with symbols' );
+ $this->addOption( 'norm', 'Apply a comma-separated list of normalization functions to ' .
+ 'both the expected and actual output in order to resolve ' .
+ 'irrelevant differences. The accepted normalization functions ' .
+ 'are: removeTbody to remove <tbody> tags; and trimWhitespace ' .
+ 'to trim whitespace from the start and end of text nodes.',
+ false, true );
+ $this->addOption( 'use-tidy-config', 'Use the wiki\'s Tidy configuration instead of known-good' .
+ 'defaults.' );
+ }
+
+ public function finalSetup() {
+ parent::finalSetup();
+ self::requireTestsAutoloader();
+ TestSetup::applyInitialConfig();
+ }
+
+ public function execute() {
+ global $wgParserTestFiles, $wgDBtype;
+
+ // Cases of weird db corruption were encountered when running tests on earlyish
+ // versions of SQLite
+ if ( $wgDBtype == 'sqlite' ) {
+ $db = wfGetDB( DB_MASTER );
+ $version = $db->getServerVersion();
+ if ( version_compare( $version, '3.6' ) < 0 ) {
+ die( "Parser tests require SQLite version 3.6 or later, you have $version\n" );
+ }
+ }
+
+ // Print out software version to assist with locating regressions
+ $version = SpecialVersion::getVersion( 'nodb' );
+ echo "This is MediaWiki version {$version}.\n\n";
+
+ // Only colorize output if stdout is a terminal.
+ $color = !wfIsWindows() && Maintenance::posix_isatty( 1 );
+
+ if ( $this->hasOption( 'color' ) ) {
+ switch ( $this->getOption( 'color' ) ) {
+ case 'no':
+ $color = false;
+ break;
+ case 'yes':
+ default:
+ $color = true;
+ break;
+ }
+ }
+
+ $record = $this->hasOption( 'record' );
+ $compare = $this->hasOption( 'compare' );
+
+ $regex = $this->getOption( 'filter', $this->getOption( 'regex', false ) );
+ if ( $regex !== false ) {
+ $regex = "/$regex/i";
+
+ if ( $record ) {
+ echo "Warning: --record cannot be used with --regex, disabling --record\n";
+ $record = false;
+ }
+ }
+
+ $term = $color
+ ? new AnsiTermColorer()
+ : new DummyTermColorer();
+
+ $recorder = new MultiTestRecorder;
+
+ $recorder->addRecorder( new ParserTestPrinter(
+ $term,
+ [
+ 'showDiffs' => !$this->hasOption( 'quick' ),
+ 'showProgress' => !$this->hasOption( 'quiet' ),
+ 'showFailure' => !$this->hasOption( 'quiet' )
+ || ( !$record && !$compare ), // redundant output
+ 'showOutput' => $this->hasOption( 'show-output' ),
+ 'useDwdiff' => $this->hasOption( 'dwdiff' ),
+ 'markWhitespace' => $this->hasOption( 'mark-ws' ),
+ ]
+ ) );
+
+ $recorderLB = false;
+ if ( $record || $compare ) {
+ $recorderLB = wfGetLBFactory()->newMainLB();
+ // This connection will have the wiki's table prefix, not parsertest_
+ $recorderDB = $recorderLB->getConnection( DB_MASTER );
+
+ // Add recorder before previewer because recorder will create the
+ // DB table if it doesn't exist
+ if ( $record ) {
+ $recorder->addRecorder( new DbTestRecorder( $recorderDB ) );
+ }
+ $recorder->addRecorder( new DbTestPreviewer(
+ $recorderDB,
+ function ( $name ) use ( $regex ) {
+ // Filter reports of old tests by the filter regex
+ if ( $regex === false ) {
+ return true;
+ } else {
+ return (bool)preg_match( $regex, $name );
+ }
+ } ) );
+ }
+
+ // Default parser tests and any set from extensions or local config
+ $files = $this->getOption( 'file', $wgParserTestFiles );
+
+ $norm = $this->hasOption( 'norm' ) ? explode( ',', $this->getOption( 'norm' ) ) : [];
+
+ $tester = new ParserTestRunner( $recorder, [
+ 'norm' => $norm,
+ 'regex' => $regex,
+ 'keep-uploads' => $this->hasOption( 'keep-uploads' ),
+ 'run-disabled' => $this->hasOption( 'run-disabled' ),
+ 'run-parsoid' => $this->hasOption( 'run-parsoid' ),
+ 'use-tidy-config' => $this->hasOption( 'use-tidy-config' ),
+ 'file-backend' => $this->getOption( 'file-backend' ),
+ 'upload-dir' => $this->getOption( 'upload-dir' ),
+ ] );
+
+ $ok = $tester->runTestsFromFiles( $files );
+ if ( $recorderLB ) {
+ $recorderLB->closeAll();
+ }
+ if ( !$ok ) {
+ exit( 1 );
+ }
+ }
+}
+
+$maintClass = 'ParserTestsMaintenance';
+require_once RUN_MAINTENANCE_IF_MAIN;
#
# You can also set the following parser properties via test options:
# wgEnableUploads, wgAllowExternalImages, wgMaxTocLevel,
-# wgLinkHolderBatchSize, wgRawHtml, wgInterwikiMagic
+# wgLinkHolderBatchSize, wgRawHtml, wgInterwikiMagic,
+# wgEnableMagicLinks
#
# For testing purposes, temporary articles can created:
# !!article / NAMESPACE:TITLE / !!text / ARTICLE TEXT / !!endarticle
| c</pre>
!!end
+!! test
+2g. Indented table markup mixed with indented pre content (proposed in bug 6200)
+!! wikitext
+ <table>
+ <tr>
+ <td>
+ Text that should be rendered preformatted
+ </td>
+ </tr>
+ </table>
+!! html
+ <table>
+ <tr>
+ <td>
+<pre>Text that should be rendered preformatted
+</pre>
+ </td>
+ </tr>
+ </table>
+
+!! end
+
!!test
3a. Indent-Pre and block tags (single-line html)
!! wikitext
<span typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"ho\">ha</div>"}},"i":0}}]}'>ho">ha</span>
!! end
+## We don't support roundtripping of these attributes in Parsoid.
+## Selective serialization takes care of preventing dirty diffs.
+## But, on edits, we dirty-diff the invalid attribute text.
!! test
-Indented table markup mixed with indented pre content (proposed in bug 6200)
+Invalid text in table attributes should be discarded
+!! options
+parsoid=wt2html
!! wikitext
- <table>
- <tr>
- <td>
- Text that should be rendered preformatted
- </td>
- </tr>
- </table>
-!! html
- <table>
- <tr>
- <td>
-<pre>Text that should be rendered preformatted
-</pre>
- </td>
- </tr>
- </table>
+{| <span>boo</span> style='border:1px solid black'
+| <span>boo</span> style='color:blue' | 1
+|<span>boo</span> style='color:blue'| 2
+|}
+!! html/php
+<table style="border:1px solid black">
+<tr>
+<td style="color:blue"> 1
+</td>
+<td style="color:blue"> 2
+</td></tr></table>
+!! html/parsoid
+<table style="border:1px solid black">
+<tr>
+<td style="color:blue"> 1</td>
+<td style="color:blue"> 2</td>
+</tr>
+</table>
+!! end
+
+!! test
+Invalid text in table attributes should be preserved by selective serializer
+!! options
+parsoid={
+ "modes": ["selser"],
+ "changes": [
+ ["td:first-child", "text", "abc"],
+ ["td + td", "text", "xyz"]
+ ]
+}
+!! wikitext
+{| <span>boo</span> style='border:1px solid black'
+| <span>boo</span> style='color:blue' | 1
+|<span>boo</span> style='color:blue'| 2
+|}
+!! wikitext/edited
+{| <span>boo</span> style='border:1px solid black'
+| <span>boo</span> style='color:blue' |abc
+|<span>boo</span> style='color:blue'|xyz
+|}
!! end
!! test
<p><a rel="mw:WikiLink" href="./User:Test/123" title="User:Test/123" data-parsoid='{"stx":"simple","a":{"href":"./User:Test/123"},"sa":{"href":"/123"}}'>/123</a></p>
!! end
+!! test
+Ensure that transclusion titles are not url-decoded
+!! options
+subpage title=[[Test]]
+parsoid=wt2html
+!! wikitext
+{{Bar%C3%A9}} {{/Bar%C3%A9}}
+!! html/php
+<p>{{Bar%C3%A9}} {{/Bar%C3%A9}}
+</p>
+!! html/parsoid
+<p>{{Bar%C3%A9}} {{/Bar%C3%A9}}</p>
+!! end
+
!! test
Purely hash wikilink
!! options
<p>X<a rel="mw:ExtLink" href="//tools.ietf.org/html/rfc1234">foo</a></p>
!! end
+!! test
+Magic links: All disabled (T47942)
+!! options
+wgEnableMagicLinks={"ISBN":false, "PMID":false, "RFC":false}
+!! wikitext
+ISBN 0-306-40615-2
+PMID 1234
+RFC 4321
+!! html/php
+<p>ISBN 0-306-40615-2
+PMID 1234
+RFC 4321
+</p>
+!! end
+
###
### Templates
####
!! wikitext
[[Category:MediaWiki User's Guide]]
!! html
-<a href="/wiki/Category:MediaWiki_User%27s_Guide" title="Category:MediaWiki User's Guide">MediaWiki User's Guide</a>
+cat=MediaWiki_User's_Guide sort=
!! end
!! test
!! wikitext
[[Category:MediaWiki User's Guide|Foo]]
!! html
-<a href="/wiki/Category:MediaWiki_User%27s_Guide" title="Category:MediaWiki User's Guide">MediaWiki User's Guide</a>
+cat=MediaWiki_User's_Guide sort=Foo
!! end
!! test
!! wikitext
[[Category:MediaWiki User's Guide|MediaWiki User's Guide]]
!! html
-<a href="/wiki/Category:MediaWiki_User%27s_Guide" title="Category:MediaWiki User's Guide">MediaWiki User's Guide</a>
+cat=MediaWiki_User's_Guide sort=MediaWiki User's Guide
!! end
!! test
parsoid=wt2html
!! wikitext
{{../../../../More than parent}}
-!! html
+!! html/php
<p>{{../../../../More than parent}}
</p>
+!! html/parsoid
+<p>{{../../../../More than parent}}</p>
!! end
!! test
!! wikitext
[[Category:МедиаWики Усер'с Гуиде]]
!! html
-<a href="/wiki/%D0%9A%D0%B0%D1%82%D0%B5%D0%B3%D0%BE%D1%80%D0%B8%D1%98%D0%B0:MediaWiki_User%27s_Guide" title="Категорија:MediaWiki User's Guide">MediaWiki User's Guide</a>
+cat=МедиаWики_Усер'с_Гуиде sort=
!! end
!! wikitext
[[A]][[Category:分类]]
!! html/php
-<a href="/wiki/Category:%E5%88%86%E7%B1%BB" title="Category:分类">分类</a>
+cat=分类 sort=
!! html/parsoid
<p><a rel="mw:WikiLink" href="A" title="A">A</a></p>
<link rel="mw:PageProp/Category" href="Category:分类"/>
unclosed internal link XSS (T137264)
!! wikitext
[[#%3Cscript%3Ealert(1)%3C/script%3E|
-!! html
+!! html/php
<p>[[#<script>alert(1)</script>|
</p>
+!! html/parsoid
+<p>[[#%3Cscript%3Ealert(1)%3C/script%3E|</p>
!! end
+++ /dev/null
-<?php
-/**
- * A basic extension that's used by the parser tests to test whether input and
- * arguments are passed to extensions properly.
- *
- * Copyright © 2005, 2006 Ævar Arnfjörð Bjarmason
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Testing
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- */
-
-class ParserTestParserHook {
-
- static function setup( &$parser ) {
- $parser->setHook( 'tag', [ __CLASS__, 'dumpHook' ] );
- $parser->setHook( 'tåg', [ __CLASS__, 'dumpHook' ] );
- $parser->setHook( 'statictag', [ __CLASS__, 'staticTagHook' ] );
- return true;
- }
-
- static function dumpHook( $in, $argv ) {
- return "<pre>\n" .
- var_export( $in, true ) . "\n" .
- var_export( $argv, true ) . "\n" .
- "</pre>";
- }
-
- static function staticTagHook( $in, $argv, $parser ) {
- if ( !count( $argv ) ) {
- $parser->static_tag_buf = $in;
- return '';
- } elseif ( count( $argv ) === 1 && isset( $argv['action'] )
- && $argv['action'] === 'flush' && $in === null
- ) {
- // Clear the buffer, we probably don't need to
- if ( isset( $parser->static_tag_buf ) ) {
- $tmp = $parser->static_tag_buf;
- } else {
- $tmp = '';
- }
- $parser->static_tag_buf = null;
- return $tmp;
- } else { // wtf?
- return
- "\nCall this extension as <statictag>string</statictag> or as" .
- " <statictag action=flush/>, not in any other way.\n" .
- "text: " . var_export( $in, true ) . "\n" .
- "argv: " . var_export( $argv, true ) . "\n";
- }
- }
-}
+++ /dev/null
-<?php
-/**
- * MediaWiki parser test suite
- *
- * Copyright © 2004 Brion Vibber <brion@pobox.com>
- * https://www.mediawiki.org/
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Testing
- */
-
-define( 'MW_PARSER_TEST', true );
-
-$options = [ 'quick', 'color', 'quiet', 'help', 'show-output',
- 'record', 'run-disabled', 'run-parsoid', 'dwdiff', 'mark-ws' ];
-$optionsWithArgs = [ 'regex', 'filter', 'seed', 'setversion', 'file', 'norm' ];
-
-require_once __DIR__ . '/../maintenance/commandLine.inc';
-require_once __DIR__ . '/TestsAutoLoader.php';
-
-if ( isset( $options['help'] ) ) {
- echo <<<ENDS
-MediaWiki $wgVersion parser test suite
-Usage: php parserTests.php [options...]
-
-Options:
- --quick Suppress diff output of failed tests
- --quiet Suppress notification of passed tests (shows only failed tests)
- --show-output Show expected and actual output
- --color[=yes|no] Override terminal detection and force color output on or off
- use wgCommandLineDarkBg = true; if your term is dark
- --regex Only run tests whose descriptions which match given regex
- --filter Alias for --regex
- --file=<testfile> Run test cases from a custom file instead of parserTests.txt
- --record Record tests in database
- --compare Compare with recorded results, without updating the database.
- --setversion When using --record, set the version string to use (useful
- with git-svn so that you can get the exact revision)
- --keep-uploads Re-use the same upload directory for each test, don't delete it
- --fuzz Do a fuzz test instead of a normal test
- --seed <n> Start the fuzz test from the specified seed
- --run-disabled run disabled tests
- --run-parsoid run parsoid tests (normally disabled)
- --dwdiff Use dwdiff to display diff output
- --mark-ws Mark whitespace in diffs by replacing it with symbols
- --norm=<funcs> Apply a comma-separated list of normalization functions to
- both the expected and actual output in order to resolve
- irrelevant differences. The accepted normalization functions
- are: removeTbody to remove <tbody> tags; and trimWhitespace
- to trim whitespace from the start and end of text nodes.
- --use-tidy-config Use the wiki's Tidy configuration instead of known-good
- defaults.
- --help Show this help message
-
-ENDS;
- exit( 0 );
-}
-
-# Cases of weird db corruption were encountered when running tests on earlyish
-# versions of SQLite
-if ( $wgDBtype == 'sqlite' ) {
- $db = wfGetDB( DB_MASTER );
- $version = $db->getServerVersion();
- if ( version_compare( $version, '3.6' ) < 0 ) {
- die( "Parser tests require SQLite version 3.6 or later, you have $version\n" );
- }
-}
-
-$tester = new ParserTest( $options );
-
-if ( isset( $options['file'] ) ) {
- $files = [ $options['file'] ];
-} else {
- // Default parser tests and any set from extensions or local config
- $files = $wgParserTestFiles;
-}
-
-# Print out software version to assist with locating regressions
-$version = SpecialVersion::getVersion( 'nodb' );
-echo "This is MediaWiki version {$version}.\n\n";
-
-if ( isset( $options['fuzz'] ) ) {
- $tester->fuzzTest( $files );
-} else {
- $ok = $tester->runTestsFromFiles( $files );
- exit( $ok ? 0 : 1 );
-}
parser:
${PU} --group Parser
-parserfuzz:
- @echo "******************************************************************"
- @echo "* This WILL kill your computer by eating all memory AND all swap *"
- @echo "* *"
- @echo "* If you are on a production machine. ABORT NOW!! *"
- @echo "* Press control+C to stop *"
- @echo "* *"
- @echo "******************************************************************"
- ${PU} --group Parser,ParserFuzz
noparser:
- ${PU} --exclude-group Parser,Broken,ParserFuzz,Stub
+ ${PU} --exclude-group Parser,Broken,Stub
safe:
- ${PU} --exclude-group Broken,ParserFuzz,Destructive,Stub
+ ${PU} --exclude-group Broken,Destructive,Stub
databaseless:
- ${PU} --exclude-group Broken,ParserFuzz,Destructive,Database,Stub
+ ${PU} --exclude-group Broken,Destructive,Database,Stub
database:
- ${PU} --exclude-group Broken,ParserFuzz,Destructive,Stub --group Database
+ ${PU} --exclude-group Broken,Destructive,Stub --group Database
list-groups:
${PU} --list-groups
public static function setUpBeforeClass() {
parent::setUpBeforeClass();
- // NOTE: Usually, PHPUnitMaintClass::finalSetup already called this,
- // but let's make doubly sure.
- self::prepareServices( new GlobalVarConfig() );
+ // Get the service locator, and reset services if it's not done already
+ self::$serviceLocator = self::prepareServices( new GlobalVarConfig() );
}
/**
*
* @param Config $bootstrapConfig The bootstrap config to use with the new
* MediaWikiServices. Only used for the first call to this method.
+ * @return MediaWikiServices
*/
public static function prepareServices( Config $bootstrapConfig ) {
- static $servicesPrepared = false;
+ static $services = null;
- if ( $servicesPrepared ) {
- return;
- } else {
- $servicesPrepared = true;
+ if ( !$services ) {
+ $services = self::resetGlobalServices( $bootstrapConfig );
}
-
- self::resetGlobalServices( $bootstrapConfig );
+ return $services;
}
/**
* Reset global services, and install testing environment.
* This is the testing equivalent of MediaWikiServices::resetGlobalInstance().
* This should only be used to set up the testing environment, not when
- * running unit tests. Use overrideMwServices() for that.
+ * running unit tests. Use MediaWikiTestCase::overrideMwServices() for that.
*
* @see MediaWikiServices::resetGlobalInstance()
* @see prepareServices()
- * @see overrideMwServices()
+ * @see MediaWikiTestCase::overrideMwServices()
*
* @param Config|null $bootstrapConfig The bootstrap config to use with the new
* MediaWikiServices.
MediaWikiServices::resetGlobalInstance( $testConfig );
- self::$serviceLocator = MediaWikiServices::getInstance();
+ $serviceLocator = MediaWikiServices::getInstance();
self::installTestServices(
$oldConfigFactory,
- self::$serviceLocator
+ $serviceLocator
);
+ return $serviceLocator;
}
/**
$defaultOverrides->set( 'ObjectCaches', $objectCaches );
$defaultOverrides->set( 'MainCacheType', CACHE_NONE );
+ $defaultOverrides->set( 'JobTypeConf', [ 'default' => [ 'class' => 'JobQueueMemory' ] ] );
// Use a fast hash algorithm to hash passwords.
$defaultOverrides->set( 'PasswordDefault', 'A' );
* @throws MWException If the database table prefix is already $prefix
*/
public static function setupTestDB( DatabaseBase $db, $prefix ) {
+ if ( self::$dbSetup ) {
+ return;
+ }
+
if ( $db->tablePrefix() === $prefix ) {
throw new MWException(
'Cannot run unit tests, the database prefix is already "' . $prefix . '"' );
}
- if ( self::$dbSetup ) {
- return;
- }
-
// TODO: the below should be re-written as soon as LBFactory, LoadBalancer,
// and DatabaseBase no longer use global state.
->setConstructorArgs( [ $resourceLoader, $request ] )
->setMethods( [ 'getDirection' ] )
->getMock();
- $ctx->expects( $this->any() )->method( 'getDirection' )->will(
- $this->returnValue( $dir )
- );
+ $ctx->method( 'getDirection' )->willReturn( $dir );
return $ctx;
}
$this->assertEquals( '', $req->getText( 'z' ) );
}
+ // Integration test for parent method.
+ public function testGetVal() {
+ $req = new FauxRequest( [ 'crlf' => "A\r\nb" ] );
+ $this->assertSame( "A\r\nb", $req->getVal( 'crlf' ), 'CRLF' );
+ }
+
+ // Integration test for parent method.
+ public function testGetRawVal() {
+ $req = new FauxRequest( [
+ 'x' => 'Value',
+ 'y' => [ 'a' ],
+ 'crlf' => "A\r\nb"
+ ] );
+ $this->assertSame( 'Value', $req->getRawVal( 'x' ) );
+ $this->assertSame( null, $req->getRawVal( 'z' ), 'Not found' );
+ $this->assertSame( null, $req->getRawVal( 'y' ), 'Array is ignored' );
+ $this->assertSame( "A\r\nb", $req->getRawVal( 'crlf' ), 'CRLF' );
+ }
+
/**
* @covers FauxRequest::getValues
*/
/**
* @dataProvider provideDetectServer
* @covers WebRequest::detectServer
+ * @covers WebRequest::detectProtocol
*/
public function testDetectServer( $expected, $input, $description ) {
+ $this->setMwGlobals( 'wgAssumeProxiesUseDefaultProtocolPorts', true );
+
$_SERVER = $input;
$result = WebRequest::detectServer();
$this->assertEquals( $expected, $result, $description );
],
'Secure off'
],
+ [
+ 'https://x',
+ [
+ 'HTTP_HOST' => 'x',
+ 'HTTP_X_FORWARDED_PROTO' => 'https',
+ ],
+ 'Forwarded HTTPS'
+ ],
+ [
+ 'https://x',
+ [
+ 'HTTP_HOST' => 'x',
+ 'HTTPS' => 'off',
+ 'SERVER_PORT' => '81',
+ 'HTTP_X_FORWARDED_PROTO' => 'https',
+ ],
+ 'Forwarded HTTPS'
+ ],
[
'http://y',
[
];
}
+ protected function mockWebRequest( $data = [] ) {
+ // Cannot use PHPUnit getMockBuilder() as it does not support
+ // overriding protected properties afterwards
+ $reflection = new ReflectionClass( 'WebRequest' );
+ $req = $reflection->newInstanceWithoutConstructor();
+
+ $prop = $reflection->getProperty( 'data' );
+ $prop->setAccessible( true );
+ $prop->setValue( $req, $data );
+
+ $prop = $reflection->getProperty( 'requestTime' );
+ $prop->setAccessible( true );
+ $prop->setValue( $req, microtime( true ) );
+
+ return $req;
+ }
+
+ /**
+ * @covers WebRequest::getElapsedTime
+ */
+ public function testGetElapsedTime() {
+ $req = $this->mockWebRequest();
+ $this->assertGreaterThanOrEqual( 0.0, $req->getElapsedTime() );
+ $this->assertEquals( 0.0, $req->getElapsedTime(), '', /*delta*/ 0.2 );
+ }
+
+ /**
+ * @covers WebRequest::getVal
+ * @covers WebRequest::getGPCVal
+ * @covers WebRequest::normalizeUnicode
+ */
+ public function testGetValNormal() {
+ // Assert that WebRequest normalises GPC data using UtfNormal\Validator
+ $input = "a \x00 null";
+ $normal = "a \xef\xbf\xbd null";
+ $req = $this->mockWebRequest( [ 'x' => $input, 'y' => [ $input, $input ] ] );
+ $this->assertSame( $normal, $req->getVal( 'x' ) );
+ $this->assertNotSame( $input, $req->getVal( 'x' ) );
+ $this->assertSame( [ $normal, $normal ], $req->getArray( 'y' ) );
+ }
+
+ /**
+ * @covers WebRequest::getVal
+ * @covers WebRequest::getGPCVal
+ */
+ public function testGetVal() {
+ $req = $this->mockWebRequest( [ 'x' => 'Value', 'y' => [ 'a' ], 'crlf' => "A\r\nb" ] );
+ $this->assertSame( 'Value', $req->getVal( 'x' ), 'Simple value' );
+ $this->assertSame( null, $req->getVal( 'z' ), 'Not found' );
+ $this->assertSame( null, $req->getVal( 'y' ), 'Array is ignored' );
+ $this->assertSame( "A\r\nb", $req->getVal( 'crlf' ), 'CRLF' );
+ }
+
+ /**
+ * @covers WebRequest::getRawVal
+ */
+ public function testGetRawVal() {
+ $req = $this->mockWebRequest( [
+ 'x' => 'Value',
+ 'y' => [ 'a' ],
+ 'crlf' => "A\r\nb"
+ ] );
+ $this->assertSame( 'Value', $req->getRawVal( 'x' ) );
+ $this->assertSame( null, $req->getRawVal( 'z' ), 'Not found' );
+ $this->assertSame( null, $req->getRawVal( 'y' ), 'Array is ignored' );
+ $this->assertSame( "A\r\nb", $req->getRawVal( 'crlf' ), 'CRLF' );
+ }
+
+ /**
+ * @covers WebRequest::getArray
+ */
+ public function testGetArray() {
+ $req = $this->mockWebRequest( [ 'x' => 'Value', 'y' => [ 'a', 'b' ] ] );
+ $this->assertSame( [ 'Value' ], $req->getArray( 'x' ), 'Value becomes array' );
+ $this->assertSame( null, $req->getArray( 'z' ), 'Not found' );
+ $this->assertSame( [ 'a', 'b' ], $req->getArray( 'y' ) );
+ }
+
+ /**
+ * @covers WebRequest::getIntArray
+ */
+ public function testGetIntArray() {
+ $req = $this->mockWebRequest( [ 'x' => [ 'Value' ], 'y' => [ '0', '4.2', '-2' ] ] );
+ $this->assertSame( [ 0 ], $req->getIntArray( 'x' ), 'Text becomes 0' );
+ $this->assertSame( null, $req->getIntArray( 'z' ), 'Not found' );
+ $this->assertSame( [ 0, 4, -2 ], $req->getIntArray( 'y' ) );
+ }
+
+ /**
+ * @covers WebRequest::getInt
+ */
+ public function testGetInt() {
+ $req = $this->mockWebRequest( [
+ 'x' => 'Value',
+ 'y' => [ 'a' ],
+ 'zero' => '0',
+ 'answer' => '4.2',
+ 'neg' => '-2',
+ ] );
+ $this->assertSame( 0, $req->getInt( 'x' ), 'Text' );
+ $this->assertSame( 0, $req->getInt( 'y' ), 'Array' );
+ $this->assertSame( 0, $req->getInt( 'z' ), 'Not found' );
+ $this->assertSame( 0, $req->getInt( 'zero' ) );
+ $this->assertSame( 4, $req->getInt( 'answer' ) );
+ $this->assertSame( -2, $req->getInt( 'neg' ) );
+ }
+
+ /**
+ * @covers WebRequest::getIntOrNull
+ */
+ public function testGetIntOrNull() {
+ $req = $this->mockWebRequest( [
+ 'x' => 'Value',
+ 'y' => [ 'a' ],
+ 'zero' => '0',
+ 'answer' => '4.2',
+ 'neg' => '-2',
+ ] );
+ $this->assertSame( null, $req->getIntOrNull( 'x' ), 'Text' );
+ $this->assertSame( null, $req->getIntOrNull( 'y' ), 'Array' );
+ $this->assertSame( null, $req->getIntOrNull( 'z' ), 'Not found' );
+ $this->assertSame( 0, $req->getIntOrNull( 'zero' ) );
+ $this->assertSame( 4, $req->getIntOrNull( 'answer' ) );
+ $this->assertSame( -2, $req->getIntOrNull( 'neg' ) );
+ }
+
+ /**
+ * @covers WebRequest::getFloat
+ */
+ public function testGetFloat() {
+ $req = $this->mockWebRequest( [
+ 'x' => 'Value',
+ 'y' => [ 'a' ],
+ 'zero' => '0',
+ 'answer' => '4.2',
+ 'neg' => '-2',
+ ] );
+ $this->assertSame( 0.0, $req->getFloat( 'x' ), 'Text' );
+ $this->assertSame( 0.0, $req->getFloat( 'y' ), 'Array' );
+ $this->assertSame( 0.0, $req->getFloat( 'z' ), 'Not found' );
+ $this->assertSame( 0.0, $req->getFloat( 'zero' ) );
+ $this->assertSame( 4.2, $req->getFloat( 'answer' ) );
+ $this->assertSame( -2.0, $req->getFloat( 'neg' ) );
+ }
+
+ /**
+ * @covers WebRequest::getBool
+ */
+ public function testGetBool() {
+ $req = $this->mockWebRequest( [
+ 'x' => 'Value',
+ 'y' => [ 'a' ],
+ 'zero' => '0',
+ 'f' => 'false',
+ 't' => 'true',
+ ] );
+ $this->assertSame( true, $req->getBool( 'x' ), 'Text' );
+ $this->assertSame( false, $req->getBool( 'y' ), 'Array' );
+ $this->assertSame( false, $req->getBool( 'z' ), 'Not found' );
+ $this->assertSame( false, $req->getBool( 'zero' ) );
+ $this->assertSame( true, $req->getBool( 'f' ) );
+ $this->assertSame( true, $req->getBool( 't' ) );
+ }
+
+ public static function provideFuzzyBool() {
+ return [
+ [ 'Text', true ],
+ [ '', false, '(empty string)' ],
+ [ '0', false ],
+ [ '1', true ],
+ [ 'false', false ],
+ [ 'true', true ],
+ [ 'False', false ],
+ [ 'True', true ],
+ [ 'FALSE', false ],
+ [ 'TRUE', true ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideFuzzyBool
+ * @covers WebRequest::getFuzzyBool
+ */
+ public function testGetFuzzyBool( $value, $expected, $message = null ) {
+ $req = $this->mockWebRequest( [ 'x' => $value ] );
+ $this->assertSame( $expected, $req->getFuzzyBool( 'x' ), $message ?: "Value: '$value'" );
+ }
+
+ /**
+ * @covers WebRequest::getFuzzyBool
+ */
+ public function testGetFuzzyBoolDefault() {
+ $req = $this->mockWebRequest();
+ $this->assertSame( false, $req->getFuzzyBool( 'z' ), 'Not found' );
+ }
+
+ /**
+ * @covers WebRequest::getCheck
+ */
+ public function testGetCheck() {
+ $req = $this->mockWebRequest( [ 'x' => 'Value', 'zero' => '0' ] );
+ $this->assertSame( false, $req->getCheck( 'z' ), 'Not found' );
+ $this->assertSame( true, $req->getCheck( 'x' ), 'Text' );
+ $this->assertSame( true, $req->getCheck( 'zero' ) );
+ }
+
+ /**
+ * @covers WebRequest::getText
+ */
+ public function testGetText() {
+ // Avoid FauxRequest (overrides getText)
+ $req = $this->mockWebRequest( [ 'crlf' => "Va\r\nlue" ] );
+ $this->assertSame( "Va\nlue", $req->getText( 'crlf' ), 'CR stripped' );
+ }
+
+ /**
+ * @covers WebRequest::getValues
+ */
+ public function testGetValues() {
+ $values = [ 'x' => 'Value', 'y' => '' ];
+ // Avoid FauxRequest (overrides getValues)
+ $req = $this->mockWebRequest( $values );
+ $this->assertSame( $values, $req->getValues() );
+ $this->assertSame( [ 'x' => 'Value' ], $req->getValues( 'x' ), 'Specific keys' );
+ }
+
+ /**
+ * @covers WebRequest::getValueNames
+ */
+ public function testGetValueNames() {
+ $req = $this->mockWebRequest( [ 'x' => 'Value', 'y' => '' ] );
+ $this->assertSame( [ 'x', 'y' ], $req->getValueNames() );
+ $this->assertSame( [ 'x' ], $req->getValueNames( [ 'y' ] ), 'Exclude keys' );
+ }
+
/**
* @dataProvider provideGetIP
* @covers WebRequest::getIP
[ 'en-gb' => 1, 'en-us' => '1' ],
'Two equally prefered English variants'
],
+ [ '_', [], 'Invalid input' ],
];
}
$centralId = CentralIdLookup::factory()->centralIdFromLocalUser( $user->getUser() );
$this->assertNotEquals( 0, $centralId, 'sanity check' );
+ $password = 'ngfhmjm64hv0854493hsj5nncjud2clk';
$passwordFactory = new PasswordFactory();
$passwordFactory->init( RequestContext::getMain()->getConfig() );
// A is unsalted MD5 (thus fast) ... we don't care about security here, this is test only
- $passwordHash = $passwordFactory->newFromPlaintext( 'foobaz' );
+ $passwordHash = $passwordFactory->newFromPlaintext( $password );
$dbw = wfGetDB( DB_MASTER );
$dbw->insert(
$ret = $this->doApiRequest( [
'action' => 'login',
'lgname' => $lgName,
- 'lgpassword' => 'foobaz',
+ 'lgpassword' => $password,
] );
$result = $ret[0];
'action' => 'login',
'lgtoken' => $token,
'lgname' => $lgName,
- 'lgpassword' => 'foobaz',
+ 'lgpassword' => $password,
], $ret[2] );
$result = $ret[0];
--- /dev/null
+<?php
+
+class JsonContentHandlerTest extends MediaWikiTestCase {
+
+ /**
+ * @covers JsonContentHandler::makeEmptyContent
+ */
+ public function testMakeEmptyContent() {
+ $handler = new JsonContentHandler();
+ $content = $handler->makeEmptyContent();
+ $this->assertInstanceOf( JsonContent::class, $content );
+ $this->assertTrue( $content->isValid() );
+ }
+}
->setMethods( [ 'fetchRow', 'query' ] )
->getMock();
- $db->expects( $this->any() )
- ->method( 'query' )
+ $db->method( 'query' )
->with( $this->anything() )
- ->will(
- $this->returnValue( null )
- );
+ ->willReturn( null );
- $db->expects( $this->any() )
- ->method( 'fetchRow' )
+ $db->method( 'fetchRow' )
->with( $this->anything() )
->will( $this->onConsecutiveCalls(
[ 'Tables_in_' => 'view1' ],
'getLagDetectionMethod', 'getHeartbeatData', 'getMasterServerInfo' ] )
->getMock();
- $db->expects( $this->any() )
- ->method( 'getLagDetectionMethod' )
- ->will( $this->returnValue( 'pt-heartbeat' ) );
+ $db->method( 'getLagDetectionMethod' )
+ ->willReturn( 'pt-heartbeat' );
- $db->expects( $this->any() )
- ->method( 'getMasterServerInfo' )
- ->will( $this->returnValue( [ 'serverId' => 172, 'asOf' => time() ] ) );
+ $db->method( 'getMasterServerInfo' )
+ ->willReturn( [ 'serverId' => 172, 'asOf' => time() ] );
// Fake the current time.
list( $nowSecFrac, $nowSec ) = explode( ' ', microtime() );
$ptTimeISO = $ptDateTime->format( 'Y-m-d\TH:i:s' );
$ptTimeISO .= ltrim( number_format( $ptSecFrac, 6 ), '0' );
- $db->expects( $this->any() )
- ->method( 'getHeartbeatData' )
+ $db->method( 'getHeartbeatData' )
->with( [ 'server_id' => 172 ] )
- ->will( $this->returnValue( [ $ptTimeISO, $now ] ) );
+ ->willReturn( [ $ptTimeISO, $now ] );
$db->setLBInfo( 'clusterMasterHost', 'db1052' );
$lagEst = $db->getLag();
return true;
}
+ function ping( &$rtt = null ) {
+ $rtt = 0.0;
+ return true;
+ }
+
protected function closeConnection() {
return false;
}
[ 'LBFactorySimple', 'LBFactory_Simple' ],
[ 'LBFactorySingle', 'LBFactory_Single' ],
[ 'LBFactoryMulti', 'LBFactory_Multi' ],
- [ 'LBFactoryFake', 'LBFactory_Fake' ],
];
}
// (a) First HTTP request
$mPos = new MySQLMasterPos( 'db1034-bin.000976', '843431247' );
+ $now = microtime( true );
$mockDB = $this->getMockBuilder( 'DatabaseMysql' )
->disableOriginalConstructor()
->getMock();
- $mockDB->expects( $this->any() )
- ->method( 'doneWrites' )->will( $this->returnValue( true ) );
- $mockDB->expects( $this->any() )
- ->method( 'getMasterPos' )->will( $this->returnValue( $mPos ) );
+ $mockDB->method( 'writesOrCallbacksPending' )->willReturn( true );
+ $mockDB->method( 'lastDoneWrites' )->willReturn( $now );
+ $mockDB->method( 'getMasterPos' )->willReturn( $mPos );
$lb = $this->getMockBuilder( 'LoadBalancer' )
->disableOriginalConstructor()
->getMock();
- $lb->expects( $this->any() )
- ->method( 'getConnection' )->will( $this->returnValue( $mockDB ) );
- $lb->expects( $this->any() )
- ->method( 'getServerCount' )->will( $this->returnValue( 2 ) );
- $lb->expects( $this->any() )
- ->method( 'parentInfo' )->will( $this->returnValue( [ 'id' => "main-DEFAULT" ] ) );
- $lb->expects( $this->any() )
- ->method( 'getAnyOpenConnection' )->will( $this->returnValue( $mockDB ) );
+ $lb->method( 'getConnection' )->willReturn( $mockDB );
+ $lb->method( 'getServerCount' )->willReturn( 2 );
+ $lb->method( 'parentInfo' )->willReturn( [ 'id' => "main-DEFAULT" ] );
+ $lb->method( 'getAnyOpenConnection' )->willReturn( $mockDB );
+ $lb->method( 'hasOrMadeRecentMasterChanges' )->will( $this->returnCallback(
+ function () use ( $mockDB ) {
+ $p = 0;
+ $p |= call_user_func( [ $mockDB, 'writesOrCallbacksPending' ] );
+ $p |= call_user_func( [ $mockDB, 'lastDoneWrites' ] );
+
+ return (bool)$p;
+ }
+ ) );
+ $lb->method( 'getMasterPos' )->willReturn( $mPos );
$bag = new HashBagOStuff();
$cp = new ChronologyProtector(
]
);
- $mockDB->expects( $this->exactly( 2 ) )->method( 'doneWrites' );
+ $mockDB->expects( $this->exactly( 2 ) )->method( 'writesOrCallbacksPending' );
+ $mockDB->expects( $this->exactly( 2 ) )->method( 'lastDoneWrites' );
// Nothing to wait for
$cp->initLB( $lb );
--- /dev/null
+<?php
+
+/**
+ * Test class for ReverseChronologicalPagerTest methods.
+ *
+ * @group Pager
+ *
+ * @author Geoffrey Mon <geofbot@gmail.com>
+ */
+class ReverseChronologicalPagerTest extends MediaWikiLangTestCase {
+
+ /**
+ * @covers ReverseChronologicalPager::getDateCond
+ */
+ public function testGetDateCond() {
+ $pager = $this->getMockForAbstractClass( 'ReverseChronologicalPager' );
+ $timestamp = MWTimestamp::getInstance();
+ $db = wfGetDB( DB_MASTER );
+
+ $currYear = $timestamp->format( 'Y' );
+ $currMonth = $timestamp->format( 'n' );
+
+ // Test that getDateCond sets and returns mOffset
+ $this->assertEquals( $pager->getDateCond( 2006, 6 ), $pager->mOffset );
+
+ // Test year and month
+ $pager->getDateCond( 2006, 6 );
+ $this->assertEquals( $pager->mOffset, $db->timestamp( '20060701000000' ) );
+
+ // Test year, month, and day
+ $pager->getDateCond( 2006, 6, 5 );
+ $this->assertEquals( $pager->mOffset, $db->timestamp( '20060606000000' ) );
+
+ // Test month overflow into the next year
+ $pager->getDateCond( 2006, 12 );
+ $this->assertEquals( $pager->mOffset, $db->timestamp( '20070101000000' ) );
+
+ // Test day overflow to the next month
+ $pager->getDateCond( 2006, 6, 30 );
+ $this->assertEquals( $pager->mOffset, $db->timestamp( '20060701000000' ) );
+
+ // Test invalid month (should use end of year)
+ $pager->getDateCond( 2006, -1 );
+ $this->assertEquals( $pager->mOffset, $db->timestamp( '20070101000000' ) );
+
+ // Test invalid day (should use end of month)
+ $pager->getDateCond( 2006, 6, 1337 );
+ $this->assertEquals( $pager->mOffset, $db->timestamp( '20060701000000' ) );
+
+ // Test last day of year
+ $pager->getDateCond( 2006, 12, 31 );
+ $this->assertEquals( $pager->mOffset, $db->timestamp( '20070101000000' ) );
+
+ // Test invalid day that overflows to next year
+ $pager->getDateCond( 2006, 12, 32 );
+ $this->assertEquals( $pager->mOffset, $db->timestamp( '20070101000000' ) );
+
+ // Test month past current month (should use previous year)
+ if ( $currMonth < 5 ) {
+ $pager->getDateCond( -1, 5 );
+ $this->assertEquals( $pager->mOffset, $db->timestamp( $currYear - 1 . '0601000000' ) );
+ }
+ if ( $currMonth < 12 ) {
+ $pager->getDateCond( -1, 12 );
+ $this->assertEquals( $pager->mOffset, $db->timestamp( $currYear . '0101000000' ) );
+ }
+ }
+}
+
+++ /dev/null
-<?php
-require_once __DIR__ . '/NewParserTest.php';
-
-/**
- * The UnitTest must be either a class that inherits from MediaWikiTestCase
- * or a class that provides a public static suite() method which returns
- * an PHPUnit_Framework_Test object
- *
- * @group Parser
- * @group ParserTests
- * @group Database
- */
-class MediaWikiParserTest {
-
- /**
- * @defgroup filtering_constants Filtering constants
- *
- * Limit inclusion of parser tests files coming from MediaWiki core
- * @{
- */
-
- /** Include files shipped with MediaWiki core */
- const CORE_ONLY = 1;
- /** Include non core files as set in $wgParserTestFiles */
- const NO_CORE = 2;
- /** Include anything set via $wgParserTestFiles */
- const WITH_ALL = 3; # CORE_ONLY | NO_CORE
-
- /** @} */
-
- /**
- * Get a PHPUnit test suite of parser tests. Optionally filtered with
- * $flags.
- *
- * @par Examples:
- * Get a suite of parser tests shipped by MediaWiki core:
- * @code
- * MediaWikiParserTest::suite( MediaWikiParserTest::CORE_ONLY );
- * @endcode
- * Get a suite of various parser tests, like extensions:
- * @code
- * MediaWikiParserTest::suite( MediaWikiParserTest::NO_CORE );
- * @endcode
- * Get any test defined via $wgParserTestFiles:
- * @code
- * MediaWikiParserTest::suite( MediaWikiParserTest::WITH_ALL );
- * @endcode
- *
- * @param int $flags Bitwise flag to filter out the $wgParserTestFiles that
- * will be included. Default: MediaWikiParserTest::CORE_ONLY
- *
- * @return PHPUnit_Framework_TestSuite
- */
- public static function suite( $flags = self::CORE_ONLY ) {
- if ( is_string( $flags ) ) {
- $flags = self::CORE_ONLY;
- }
- global $wgParserTestFiles, $IP;
-
- $mwTestDir = $IP . '/tests/';
-
- # Human friendly helpers
- $wantsCore = ( $flags & self::CORE_ONLY );
- $wantsRest = ( $flags & self::NO_CORE );
-
- # Will hold the .txt parser test files we will include
- $filesToTest = [];
-
- # Filter out .txt files
- foreach ( $wgParserTestFiles as $parserTestFile ) {
- $isCore = ( 0 === strpos( $parserTestFile, $mwTestDir ) );
-
- if ( $isCore && $wantsCore ) {
- self::debug( "included core parser tests: $parserTestFile" );
- $filesToTest[] = $parserTestFile;
- } elseif ( !$isCore && $wantsRest ) {
- self::debug( "included non core parser tests: $parserTestFile" );
- $filesToTest[] = $parserTestFile;
- } else {
- self::debug( "skipped parser tests: $parserTestFile" );
- }
- }
- self::debug( 'parser tests files: '
- . implode( ' ', $filesToTest ) );
-
- $suite = new PHPUnit_Framework_TestSuite;
- $testList = [];
- $counter = 0;
- foreach ( $filesToTest as $fileName ) {
- // Call the highest level directory the extension name.
- // It may or may not actually be, but it should be close
- // enough to cause there to be separate names for different
- // things, which is good enough for our purposes.
- $extensionName = basename( dirname( $fileName ) );
- $testsName = $extensionName . '__' . basename( $fileName, '.txt' );
- $escapedFileName = strtr( $fileName, [ "'" => "\\'", '\\' => '\\\\' ] );
- $parserTestClassName = ucfirst( $testsName );
-
- // Official spec for class names: http://php.net/manual/en/language.oop5.basic.php
- // Prepend 'ParserTest_' to be paranoid about it not starting with a number
- $parserTestClassName = 'ParserTest_' .
- preg_replace( '/[^a-zA-Z0-9_\x7f-\xff]/', '_', $parserTestClassName );
-
- if ( isset( $testList[$parserTestClassName] ) ) {
- // If a conflict happens, gives a very unclear fatal.
- // So as a last ditch effort to prevent that eventuality, if there
- // is a conflict, append a number.
- $counter++;
- $parserTestClassName .= $counter;
- }
- $testList[$parserTestClassName] = true;
- $parserTestClassDefinition = <<<EOT
-/**
- * @group Database
- * @group Parser
- * @group ParserTests
- * @group ParserTests_$parserTestClassName
- */
-class $parserTestClassName extends NewParserTest {
- protected \$file = '$escapedFileName';
-}
-EOT;
-
- eval( $parserTestClassDefinition );
- self::debug( "Adding test class $parserTestClassName" );
- $suite->addTestSuite( $parserTestClassName );
- }
- return $suite;
- }
-
- /**
- * Write $msg under log group 'tests-parser'
- * @param string $msg Message to log
- */
- protected static function debug( $msg ) {
- return wfDebugLog( 'tests-parser', wfGetCaller() . ' ' . $msg );
- }
-}
+++ /dev/null
-<?php
-
-use MediaWiki\MediaWikiServices;
-
-/**
- * Although marked as a stub, can work independently.
- *
- * @group Database
- * @group Parser
- * @group Stub
- *
- * @todo covers tags
- */
-class NewParserTest extends MediaWikiTestCase {
- static protected $articles = []; // Array of test articles defined by the tests
- /* The data provider is run on a different instance than the test, so it must be static
- * When running tests from several files, all tests will see all articles.
- */
- static protected $backendToUse;
-
- public $keepUploads = false;
- public $runDisabled = false;
- public $runParsoid = false;
- public $regex = '';
- public $showProgress = true;
- public $savedWeirdGlobals = [];
- public $savedGlobals = [];
- public $hooks = [];
- public $functionHooks = [];
- public $transparentHooks = [];
-
- // Fuzz test
- public $maxFuzzTestLength = 300;
- public $fuzzSeed = 0;
- public $memoryLimit = 50;
-
- /**
- * @var DjVuSupport
- */
- private $djVuSupport;
- /**
- * @var TidySupport
- */
- private $tidySupport;
-
- protected $file = false;
-
- public static function setUpBeforeClass() {
- // Inject ParserTest well-known interwikis
- ParserTest::setupInterwikis();
- }
-
- protected function setUp() {
- global $wgNamespaceAliases, $wgContLang;
- global $wgHooks, $IP;
-
- parent::setUp();
-
- // Setup CLI arguments
- if ( $this->getCliArg( 'regex' ) ) {
- $this->regex = $this->getCliArg( 'regex' );
- } else {
- # Matches anything
- $this->regex = '';
- }
-
- $this->keepUploads = $this->getCliArg( 'keep-uploads' );
-
- $tmpGlobals = [];
-
- $tmpGlobals['wgLanguageCode'] = 'en';
- $tmpGlobals['wgContLang'] = Language::factory( 'en' );
- $tmpGlobals['wgSitename'] = 'MediaWiki';
- $tmpGlobals['wgServer'] = 'http://example.org';
- $tmpGlobals['wgServerName'] = 'example.org';
- $tmpGlobals['wgScriptPath'] = '';
- $tmpGlobals['wgScript'] = '/index.php';
- $tmpGlobals['wgResourceBasePath'] = '';
- $tmpGlobals['wgStylePath'] = '/skins';
- $tmpGlobals['wgExtensionAssetsPath'] = '/extensions';
- $tmpGlobals['wgArticlePath'] = '/wiki/$1';
- $tmpGlobals['wgActionPaths'] = [];
- $tmpGlobals['wgVariantArticlePath'] = false;
- $tmpGlobals['wgEnableUploads'] = true;
- $tmpGlobals['wgUploadNavigationUrl'] = false;
- $tmpGlobals['wgThumbnailScriptPath'] = false;
- $tmpGlobals['wgLocalFileRepo'] = [
- 'class' => 'LocalRepo',
- 'name' => 'local',
- 'url' => 'http://example.com/images',
- 'hashLevels' => 2,
- 'transformVia404' => false,
- 'backend' => 'local-backend'
- ];
- $tmpGlobals['wgForeignFileRepos'] = [];
- $tmpGlobals['wgDefaultExternalStore'] = [];
- $tmpGlobals['wgParserCacheType'] = CACHE_NONE;
- $tmpGlobals['wgCapitalLinks'] = true;
- $tmpGlobals['wgNoFollowLinks'] = true;
- $tmpGlobals['wgNoFollowDomainExceptions'] = [ 'no-nofollow.org' ];
- $tmpGlobals['wgExternalLinkTarget'] = false;
- $tmpGlobals['wgThumbnailScriptPath'] = false;
- $tmpGlobals['wgUseImageResize'] = true;
- $tmpGlobals['wgAllowExternalImages'] = true;
- $tmpGlobals['wgRawHtml'] = false;
- $tmpGlobals['wgExperimentalHtmlIds'] = false;
- $tmpGlobals['wgAdaptiveMessageCache'] = true;
- $tmpGlobals['wgUseDatabaseMessages'] = true;
- $tmpGlobals['wgLocaltimezone'] = 'UTC';
- $tmpGlobals['wgGroupPermissions'] = [
- '*' => [
- 'createaccount' => true,
- 'read' => true,
- 'edit' => true,
- 'createpage' => true,
- 'createtalk' => true,
- ] ];
- $tmpGlobals['wgNamespaceProtection'] = [ NS_MEDIAWIKI => 'editinterface' ];
-
- $tmpGlobals['wgParser'] = new StubObject(
- 'wgParser', $GLOBALS['wgParserConf']['class'],
- [ $GLOBALS['wgParserConf'] ] );
-
- $tmpGlobals['wgFileExtensions'][] = 'svg';
- $tmpGlobals['wgSVGConverter'] = 'rsvg';
- $tmpGlobals['wgSVGConverters']['rsvg'] =
- '$path/rsvg-convert -w $width -h $height -o $output $input';
-
- if ( $GLOBALS['wgStyleDirectory'] === false ) {
- $tmpGlobals['wgStyleDirectory'] = "$IP/skins";
- }
-
- $tmpHooks = $wgHooks;
- $tmpHooks['ParserTestParser'][] = 'ParserTestParserHook::setup';
- $tmpHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp';
- $tmpGlobals['wgHooks'] = $tmpHooks;
- # add a namespace shadowing a interwiki link, to test
- # proper precedence when resolving links. (bug 51680)
- $tmpGlobals['wgExtraNamespaces'] = [
- 100 => 'MemoryAlpha',
- 101 => 'MemoryAlpha_talk'
- ];
-
- $tmpGlobals['wgLocalInterwikis'] = [ 'local', 'mi' ];
- # "extra language links"
- # see https://gerrit.wikimedia.org/r/111390
- $tmpGlobals['wgExtraInterlanguageLinkPrefixes'] = [ 'mul' ];
-
- // DjVu support
- $this->djVuSupport = new DjVuSupport();
- // Tidy support
- $this->tidySupport = new TidySupport();
- $tmpGlobals['wgTidyConfig'] = $this->tidySupport->getConfig();
- $tmpGlobals['wgUseTidy'] = false;
-
- $this->setMwGlobals( $tmpGlobals );
-
- $this->savedWeirdGlobals['image_alias'] = $wgNamespaceAliases['Image'];
- $this->savedWeirdGlobals['image_talk_alias'] = $wgNamespaceAliases['Image_talk'];
-
- $wgNamespaceAliases['Image'] = NS_FILE;
- $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
-
- MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
- $wgContLang->resetNamespaces(); # reset namespace cache
- ParserTest::resetTitleServices();
- MediaWikiServices::getInstance()->disableService( 'MediaHandlerFactory' );
- MediaWikiServices::getInstance()->redefineService(
- 'MediaHandlerFactory',
- function() {
- return new MockMediaHandlerFactory();
- }
- );
- }
-
- protected function tearDown() {
- global $wgNamespaceAliases, $wgContLang;
-
- $wgNamespaceAliases['Image'] = $this->savedWeirdGlobals['image_alias'];
- $wgNamespaceAliases['Image_talk'] = $this->savedWeirdGlobals['image_talk_alias'];
-
- MWTidy::destroySingleton();
-
- // Restore backends
- RepoGroup::destroySingleton();
- FileBackendGroup::destroySingleton();
-
- // Remove temporary pages from the link cache
- LinkCache::singleton()->clear();
-
- // Restore message cache (temporary pages and $wgUseDatabaseMessages)
- MessageCache::destroyInstance();
- MediaWikiServices::getInstance()->resetServiceForTesting( 'MediaHandlerFactory' );
-
- parent::tearDown();
-
- MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
- $wgContLang->resetNamespaces(); # reset namespace cache
- }
-
- public static function tearDownAfterClass() {
- ParserTest::tearDownInterwikis();
- parent::tearDownAfterClass();
- }
-
- function addDBDataOnce() {
- # disabled for performance
- # $this->tablesUsed[] = 'image';
-
- # Update certain things in site_stats
- $this->db->insert( 'site_stats',
- [ 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ],
- __METHOD__,
- [ 'IGNORE' ]
- );
-
- $user = User::newFromId( 0 );
- LinkCache::singleton()->clear(); # Avoids the odd failure at creating the nullRevision
-
- # Upload DB table entries for files.
- # We will upload the actual files later. Note that if anything causes LocalFile::load()
- # to be triggered before then, it will break via maybeUpgrade() setting the fileExists
- # member to false and storing it in cache.
- # note that the size/width/height/bits/etc of the file
- # are actually set by inspecting the file itself; the arguments
- # to recordUpload2 have no effect. That said, we try to make things
- # match up so it is less confusing to readers of the code & tests.
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) );
- if ( !$this->db->selectField( 'image', '1', [ 'img_name' => $image->getName() ] ) ) {
- $image->recordUpload2(
- '', // archive name
- 'Upload of some lame file',
- 'Some lame file',
- [
- 'size' => 7881,
- 'width' => 1941,
- 'height' => 220,
- 'bits' => 8,
- 'media_type' => MEDIATYPE_BITMAP,
- 'mime' => 'image/jpeg',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '1', 16, 36, 31 ),
- 'fileExists' => true ],
- $this->db->timestamp( '20010115123500' ), $user
- );
- }
-
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Thumb.png' ) );
- if ( !$this->db->selectField( 'image', '1', [ 'img_name' => $image->getName() ] ) ) {
- $image->recordUpload2(
- '', // archive name
- 'Upload of some lame thumbnail',
- 'Some lame thumbnail',
- [
- 'size' => 22589,
- 'width' => 135,
- 'height' => 135,
- 'bits' => 8,
- 'media_type' => MEDIATYPE_BITMAP,
- 'mime' => 'image/png',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '2', 16, 36, 31 ),
- 'fileExists' => true ],
- $this->db->timestamp( '20130225203040' ), $user
- );
- }
-
- # This image will be blacklisted in [[MediaWiki:Bad image list]]
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) );
- if ( !$this->db->selectField( 'image', '1', [ 'img_name' => $image->getName() ] ) ) {
- $image->recordUpload2(
- '', // archive name
- 'zomgnotcensored',
- 'Borderline image',
- [
- 'size' => 12345,
- 'width' => 320,
- 'height' => 240,
- 'bits' => 24,
- 'media_type' => MEDIATYPE_BITMAP,
- 'mime' => 'image/jpeg',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '3', 16, 36, 31 ),
- 'fileExists' => true ],
- $this->db->timestamp( '20010115123500' ), $user
- );
- }
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.svg' ) );
- if ( !$this->db->selectField( 'image', '1', [ 'img_name' => $image->getName() ] ) ) {
- $image->recordUpload2( '', 'Upload of some lame SVG', 'Some lame SVG', [
- 'size' => 12345,
- 'width' => 240,
- 'height' => 180,
- 'bits' => 0,
- 'media_type' => MEDIATYPE_DRAWING,
- 'mime' => 'image/svg+xml',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
- 'fileExists' => true
- ], $this->db->timestamp( '20010115123500' ), $user );
- }
-
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Video.ogv' ) );
- if ( !$this->db->selectField( 'image', '1', [ 'img_name' => $image->getName() ] ) ) {
- $image->recordUpload2( '', 'A pretty movie', 'Will it play', [
- 'size' => 12345,
- 'width' => 320,
- 'height' => 240,
- 'bits' => 0,
- 'media_type' => MEDIATYPE_VIDEO,
- 'mime' => 'application/ogg',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '', 16, 36, 32 ),
- 'fileExists' => true
- ], $this->db->timestamp( '20010115123500' ), $user );
- }
-
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Audio.oga' ) );
- if ( !$this->db->selectField( 'image', '1', [ 'img_name' => $image->getName() ] ) ) {
- $image->recordUpload2( '', 'An awesome hitsong ', 'Will it play', [
- 'size' => 12345,
- 'width' => 0,
- 'height' => 0,
- 'bits' => 0,
- 'media_type' => MEDIATYPE_AUDIO,
- 'mime' => 'application/ogg',
- 'metadata' => serialize( [] ),
- 'sha1' => Wikimedia\base_convert( '', 16, 36, 32 ),
- 'fileExists' => true
- ], $this->db->timestamp( '20010115123500' ), $user );
- }
-
- # A DjVu file
- $image = wfLocalFile( Title::makeTitle( NS_FILE, 'LoremIpsum.djvu' ) );
- if ( !$this->db->selectField( 'image', '1', [ 'img_name' => $image->getName() ] ) ) {
- $image->recordUpload2( '', 'Upload a DjVu', 'A DjVu', [
- 'size' => 3249,
- 'width' => 2480,
- 'height' => 3508,
- 'bits' => 0,
- 'media_type' => MEDIATYPE_BITMAP,
- 'mime' => 'image/vnd.djvu',
- 'metadata' => '<?xml version="1.0" ?>
-<!DOCTYPE DjVuXML PUBLIC "-//W3C//DTD DjVuXML 1.1//EN" "pubtext/DjVuXML-s.dtd">
-<DjVuXML>
-<HEAD></HEAD>
-<BODY><OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-<OBJECT height="3508" width="2480">
-<PARAM name="DPI" value="300" />
-<PARAM name="GAMMA" value="2.2" />
-</OBJECT>
-</BODY>
-</DjVuXML>',
- 'sha1' => Wikimedia\base_convert( '', 16, 36, 31 ),
- 'fileExists' => true
- ], $this->db->timestamp( '20140115123600' ), $user );
- }
- }
-
- // ParserTest setup/teardown functions
-
- /**
- * Set up the global variables for a consistent environment for each test.
- * Ideally this should replace the global configuration entirely.
- * @param array $opts
- * @param string $config
- * @return RequestContext
- */
- protected function setupGlobals( $opts = [], $config = '' ) {
- global $wgFileBackends;
- # Find out values for some special options.
- $lang =
- self::getOptionValue( 'language', $opts, 'en' );
- $variant =
- self::getOptionValue( 'variant', $opts, false );
- $maxtoclevel =
- self::getOptionValue( 'wgMaxTocLevel', $opts, 999 );
- $linkHolderBatchSize =
- self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 );
-
- $uploadDir = $this->getUploadDir();
- if ( $this->getCliArg( 'use-filebackend' ) ) {
- if ( self::$backendToUse ) {
- $backend = self::$backendToUse;
- } else {
- $name = $this->getCliArg( 'use-filebackend' );
- $useConfig = [];
- foreach ( $wgFileBackends as $conf ) {
- if ( $conf['name'] == $name ) {
- $useConfig = $conf;
- }
- }
- $useConfig['name'] = 'local-backend'; // swap name
- unset( $useConfig['lockManager'] );
- unset( $useConfig['fileJournal'] );
- $class = $useConfig['class'];
- self::$backendToUse = new $class( $useConfig );
- $backend = self::$backendToUse;
- }
- } else {
- # Replace with a mock. We do not care about generating real
- # files on the filesystem, just need to expose the file
- # informations.
- $backend = new MockFileBackend( [
- 'name' => 'local-backend',
- 'wikiId' => wfWikiID()
- ] );
- }
-
- $settings = [
- 'wgLocalFileRepo' => [
- 'class' => 'LocalRepo',
- 'name' => 'local',
- 'url' => 'http://example.com/images',
- 'hashLevels' => 2,
- 'transformVia404' => false,
- 'backend' => $backend
- ],
- 'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ),
- 'wgLanguageCode' => $lang,
- 'wgDBprefix' => $this->db->getType() != 'oracle' ? 'unittest_' : 'ut_',
- 'wgRawHtml' => self::getOptionValue( 'wgRawHtml', $opts, false ),
- 'wgNamespacesWithSubpages' => [ NS_MAIN => isset( $opts['subpage'] ) ],
- 'wgAllowExternalImages' => self::getOptionValue( 'wgAllowExternalImages', $opts, true ),
- 'wgThumbLimits' => [ self::getOptionValue( 'thumbsize', $opts, 180 ) ],
- 'wgMaxTocLevel' => $maxtoclevel,
- 'wgUseTeX' => isset( $opts['math'] ) || isset( $opts['texvc'] ),
- 'wgMathDirectory' => $uploadDir . '/math',
- 'wgDefaultLanguageVariant' => $variant,
- 'wgLinkHolderBatchSize' => $linkHolderBatchSize,
- 'wgUseTidy' => false,
- 'wgTidyConfig' => isset( $opts['tidy'] ) ? $this->tidySupport->getConfig() : null
- ];
-
- if ( $config ) {
- $configLines = explode( "\n", $config );
-
- foreach ( $configLines as $line ) {
- list( $var, $value ) = explode( '=', $line, 2 );
-
- $settings[$var] = eval( "return $value;" ); // ???
- }
- }
-
- $this->savedGlobals = [];
-
- /** @since 1.20 */
- Hooks::run( 'ParserTestGlobals', [ &$settings ] );
-
- $langObj = Language::factory( $lang );
- $settings['wgContLang'] = $langObj;
- $settings['wgLang'] = $langObj;
-
- $context = new RequestContext();
- $settings['wgOut'] = $context->getOutput();
- $settings['wgUser'] = $context->getUser();
- $settings['wgRequest'] = $context->getRequest();
-
- // We (re)set $wgThumbLimits to a single-element array above.
- $context->getUser()->setOption( 'thumbsize', 0 );
-
- foreach ( $settings as $var => $val ) {
- if ( array_key_exists( $var, $GLOBALS ) ) {
- $this->savedGlobals[$var] = $GLOBALS[$var];
- }
-
- $GLOBALS[$var] = $val;
- }
-
- MWTidy::destroySingleton();
- MagicWord::clearCache();
-
- # The entries saved into RepoGroup cache with previous globals will be wrong.
- RepoGroup::destroySingleton();
- FileBackendGroup::destroySingleton();
-
- # Create dummy files in storage
- $this->setupUploads();
-
- # Publish the articles after we have the final language set
- $this->publishTestArticles();
-
- MessageCache::destroyInstance();
-
- return $context;
- }
-
- /**
- * Get an FS upload directory (only applies to FSFileBackend)
- *
- * @return string The directory
- */
- protected function getUploadDir() {
- if ( $this->keepUploads ) {
- // Don't use getNewTempDirectory() as this is meant to persist
- $dir = wfTempDir() . '/mwParser-images';
-
- if ( is_dir( $dir ) ) {
- return $dir;
- }
- } else {
- $dir = $this->getNewTempDirectory();
- }
-
- if ( file_exists( $dir ) ) {
- wfDebug( "Already exists!\n" );
-
- return $dir;
- }
-
- return $dir;
- }
-
- /**
- * Create a dummy uploads directory which will contain a couple
- * of files in order to pass existence tests.
- *
- * @return string The directory
- */
- protected function setupUploads() {
- global $IP;
-
- $base = $this->getBaseDir();
- $backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
- $backend->prepare( [ 'dir' => "$base/local-public/3/3a" ] );
- $backend->store( [
- 'src' => "$IP/tests/phpunit/data/parser/headbg.jpg",
- 'dst' => "$base/local-public/3/3a/Foobar.jpg"
- ] );
- $backend->prepare( [ 'dir' => "$base/local-public/e/ea" ] );
- $backend->store( [
- 'src' => "$IP/tests/phpunit/data/parser/wiki.png",
- 'dst' => "$base/local-public/e/ea/Thumb.png"
- ] );
- $backend->prepare( [ 'dir' => "$base/local-public/0/09" ] );
- $backend->store( [
- 'src' => "$IP/tests/phpunit/data/parser/headbg.jpg",
- 'dst' => "$base/local-public/0/09/Bad.jpg"
- ] );
- $backend->prepare( [ 'dir' => "$base/local-public/5/5f" ] );
- $backend->store( [
- 'src' => "$IP/tests/phpunit/data/parser/LoremIpsum.djvu",
- 'dst' => "$base/local-public/5/5f/LoremIpsum.djvu"
- ] );
-
- // No helpful SVG file to copy, so make one ourselves
- $data = '<?xml version="1.0" encoding="utf-8"?>' .
- '<svg xmlns="http://www.w3.org/2000/svg"' .
- ' version="1.1" width="240" height="180"/>';
-
- $backend->prepare( [ 'dir' => "$base/local-public/f/ff" ] );
- $backend->quickCreate( [
- 'content' => $data, 'dst' => "$base/local-public/f/ff/Foobar.svg"
- ] );
- }
-
- /**
- * Restore default values and perform any necessary clean-up
- * after each test runs.
- */
- protected function teardownGlobals() {
- $this->teardownUploads();
-
- foreach ( $this->savedGlobals as $var => $val ) {
- $GLOBALS[$var] = $val;
- }
- }
-
- /**
- * Remove the dummy uploads directory
- */
- private function teardownUploads() {
- if ( $this->keepUploads ) {
- return;
- }
-
- $backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
- if ( $backend instanceof MockFileBackend ) {
- # In memory backend, so dont bother cleaning them up.
- return;
- }
-
- $base = $this->getBaseDir();
- // delete the files first, then the dirs.
- self::deleteFiles(
- [
- "$base/local-public/3/3a/Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/1000px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/100px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/120px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/137px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/1500px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/177px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/180px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/200px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/206px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/20px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/220px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/265px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/270px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/274px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/300px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/30px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/330px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/353px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/360px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/400px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/40px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/440px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/442px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/450px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/50px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/600px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/640px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/70px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/75px-Foobar.jpg",
- "$base/local-thumb/3/3a/Foobar.jpg/960px-Foobar.jpg",
-
- "$base/local-public/e/ea/Thumb.png",
-
- "$base/local-public/0/09/Bad.jpg",
-
- "$base/local-public/5/5f/LoremIpsum.djvu",
- "$base/local-thumb/5/5f/LoremIpsum.djvu/page2-2480px-LoremIpsum.djvu.jpg",
- "$base/local-thumb/5/5f/LoremIpsum.djvu/page2-3720px-LoremIpsum.djvu.jpg",
- "$base/local-thumb/5/5f/LoremIpsum.djvu/page2-4960px-LoremIpsum.djvu.jpg",
-
- "$base/local-public/f/ff/Foobar.svg",
- "$base/local-thumb/f/ff/Foobar.svg/180px-Foobar.svg.png",
- "$base/local-thumb/f/ff/Foobar.svg/2000px-Foobar.svg.png",
- "$base/local-thumb/f/ff/Foobar.svg/270px-Foobar.svg.png",
- "$base/local-thumb/f/ff/Foobar.svg/3000px-Foobar.svg.png",
- "$base/local-thumb/f/ff/Foobar.svg/360px-Foobar.svg.png",
- "$base/local-thumb/f/ff/Foobar.svg/4000px-Foobar.svg.png",
- "$base/local-thumb/f/ff/Foobar.svg/langde-180px-Foobar.svg.png",
- "$base/local-thumb/f/ff/Foobar.svg/langde-270px-Foobar.svg.png",
- "$base/local-thumb/f/ff/Foobar.svg/langde-360px-Foobar.svg.png",
-
- "$base/local-public/math/f/a/5/fa50b8b616463173474302ca3e63586b.png",
- ]
- );
- }
-
- /**
- * Delete the specified files, if they exist.
- * @param array $files Full paths to files to delete.
- */
- private static function deleteFiles( $files ) {
- $backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
- foreach ( $files as $file ) {
- $backend->delete( [ 'src' => $file ], [ 'force' => 1 ] );
- }
- foreach ( $files as $file ) {
- $tmp = FileBackend::parentStoragePath( $file );
- while ( $tmp ) {
- if ( !$backend->clean( [ 'dir' => $tmp ] )->isOK() ) {
- break;
- }
- $tmp = FileBackend::parentStoragePath( $tmp );
- }
- }
- }
-
- protected function getBaseDir() {
- return 'mwstore://local-backend';
- }
-
- public function parserTestProvider() {
- if ( $this->file === false ) {
- global $wgParserTestFiles;
- $this->file = $wgParserTestFiles[0];
- }
-
- return new TestFileDataProvider( $this->file, $this );
- }
-
- /**
- * Set the file from whose tests will be run by this instance
- * @param string $filename
- */
- public function setParserTestFile( $filename ) {
- $this->file = $filename;
- }
-
- /**
- * @group medium
- * @group ParserTests
- * @dataProvider parserTestProvider
- * @param string $desc
- * @param string $input
- * @param string $result
- * @param array $opts
- * @param array $config
- */
- public function testParserTest( $desc, $input, $result, $opts, $config ) {
- if ( $this->regex != '' && !preg_match( '/' . $this->regex . '/', $desc ) ) {
- $this->assertTrue( true ); // XXX: don't flood output with "test made no assertions"
- // $this->markTestSkipped( 'Filtered out by the user' );
- $this->teardownGlobals();
- return;
- }
-
- if ( !$this->isWikitextNS( NS_MAIN ) ) {
- // parser tests frequently assume that the main namespace contains wikitext.
- // @todo When setting up pages, force the content model. Only skip if
- // $wgtContentModelUseDB is false.
- $this->teardownGlobals();
- $this->markTestSkipped( "Main namespace does not support wikitext,"
- . "skipping parser test: $desc" );
- }
-
- wfDebug( "Running parser test: $desc\n" );
-
- $opts = $this->parseOptions( $opts );
- $context = $this->setupGlobals( $opts, $config );
-
- $user = $context->getUser();
- $options = ParserOptions::newFromContext( $context );
-
- if ( isset( $opts['title'] ) ) {
- $titleText = $opts['title'];
- } else {
- $titleText = 'Parser test';
- }
-
- $local = isset( $opts['local'] );
- $preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null;
- $parser = $this->getParser( $preprocessor );
-
- $title = Title::newFromText( $titleText );
-
- # Parser test requiring math. Make sure texvc is executable
- # or just skip such tests.
- if ( isset( $opts['math'] ) || isset( $opts['texvc'] ) ) {
- global $wgTexvc;
-
- if ( !isset( $wgTexvc ) ) {
- $this->teardownGlobals();
- $this->markTestSkipped( "SKIPPED: \$wgTexvc is not set" );
- } elseif ( !is_executable( $wgTexvc ) ) {
- $this->teardownGlobals();
- $this->markTestSkipped( "SKIPPED: texvc binary does not exist"
- . " or is not executable.\n"
- . "Current configuration is:\n\$wgTexvc = '$wgTexvc'" );
- }
- }
-
- if ( isset( $opts['djvu'] ) ) {
- if ( !$this->djVuSupport->isEnabled() ) {
- $this->teardownGlobals();
- $this->markTestSkipped( "SKIPPED: djvu binaries do not exist or are not executable.\n" );
- }
- }
-
- if ( isset( $opts['tidy'] ) ) {
- if ( !$this->tidySupport->isEnabled() ) {
- $this->teardownGlobals();
- $this->markTestSkipped( "SKIPPED: tidy extension is not installed.\n" );
- } else {
- $options->setTidy( true );
- }
- }
-
- if ( isset( $opts['pst'] ) ) {
- $out = $parser->preSaveTransform( $input, $title, $user, $options );
- } elseif ( isset( $opts['msg'] ) ) {
- $out = $parser->transformMsg( $input, $options, $title );
- } elseif ( isset( $opts['section'] ) ) {
- $section = $opts['section'];
- $out = $parser->getSection( $input, $section );
- } elseif ( isset( $opts['replace'] ) ) {
- $section = $opts['replace'][0];
- $replace = $opts['replace'][1];
- $out = $parser->replaceSection( $input, $section, $replace );
- } elseif ( isset( $opts['comment'] ) ) {
- $out = Linker::formatComment( $input, $title, $local );
- } elseif ( isset( $opts['preload'] ) ) {
- $out = $parser->getPreloadText( $input, $title, $options );
- } else {
- $output = $parser->parse( $input, $title, $options, true, true, 1337 );
- $output->setTOCEnabled( !isset( $opts['notoc'] ) );
- $out = $output->getText();
- if ( isset( $opts['tidy'] ) ) {
- $out = preg_replace( '/\s+$/', '', $out );
- }
-
- if ( isset( $opts['showtitle'] ) ) {
- if ( $output->getTitleText() ) {
- $title = $output->getTitleText();
- }
-
- $out = "$title\n$out";
- }
-
- if ( isset( $opts['showindicators'] ) ) {
- $indicators = '';
- foreach ( $output->getIndicators() as $id => $content ) {
- $indicators .= "$id=$content\n";
- }
- $out = $indicators . $out;
- }
-
- if ( isset( $opts['ill'] ) ) {
- $out = implode( ' ', $output->getLanguageLinks() );
- } elseif ( isset( $opts['cat'] ) ) {
- $outputPage = $context->getOutput();
- $outputPage->addCategoryLinks( $output->getCategories() );
- $cats = $outputPage->getCategoryLinks();
-
- if ( isset( $cats['normal'] ) ) {
- $out = implode( ' ', $cats['normal'] );
- } else {
- $out = '';
- }
- }
- $parser->mPreprocessor = null;
- }
-
- $this->teardownGlobals();
-
- $this->assertEquals( $result, $out, $desc );
- }
-
- /**
- * Run a fuzz test series
- * Draw input from a set of test files
- *
- * @todo fixme Needs some work to not eat memory until the world explodes
- *
- * @group ParserFuzz
- */
- public function testFuzzTests() {
- global $wgParserTestFiles;
-
- $files = $wgParserTestFiles;
-
- if ( $this->getCliArg( 'file' ) ) {
- $files = [ $this->getCliArg( 'file' ) ];
- }
-
- $dict = $this->getFuzzInput( $files );
- $dictSize = strlen( $dict );
- $logMaxLength = log( $this->maxFuzzTestLength );
-
- ini_set( 'memory_limit', $this->memoryLimit * 1048576 );
-
- $user = new User;
- $opts = ParserOptions::newFromUser( $user );
- $title = Title::makeTitle( NS_MAIN, 'Parser_test' );
-
- $id = 1;
-
- while ( true ) {
-
- // Generate test input
- mt_srand( ++$this->fuzzSeed );
- $totalLength = mt_rand( 1, $this->maxFuzzTestLength );
- $input = '';
-
- while ( strlen( $input ) < $totalLength ) {
- $logHairLength = mt_rand( 0, 1000000 ) / 1000000 * $logMaxLength;
- $hairLength = min( intval( exp( $logHairLength ) ), $dictSize );
- $offset = mt_rand( 0, $dictSize - $hairLength );
- $input .= substr( $dict, $offset, $hairLength );
- }
-
- $this->setupGlobals();
- $parser = $this->getParser();
-
- // Run the test
- try {
- $parser->parse( $input, $title, $opts );
- $this->assertTrue( true, "Test $id, fuzz seed {$this->fuzzSeed}" );
- } catch ( Exception $exception ) {
- $input_dump = sprintf( "string(%d) \"%s\"\n", strlen( $input ), $input );
-
- $this->assertTrue( false, "Test $id, fuzz seed {$this->fuzzSeed}. \n\n" .
- "Input: $input_dump\n\nError: {$exception->getMessage()}\n\n" .
- "Backtrace: {$exception->getTraceAsString()}" );
- }
-
- $this->teardownGlobals();
- $parser->__destruct();
-
- if ( $id % 100 == 0 ) {
- $usage = intval( memory_get_usage( true ) / $this->memoryLimit / 1048576 * 100 );
- // echo "{$this->fuzzSeed}: $numSuccess/$numTotal (mem: $usage%)\n";
- if ( $usage > 90 ) {
- $ret = "Out of memory:\n";
- $memStats = $this->getMemoryBreakdown();
-
- foreach ( $memStats as $name => $usage ) {
- $ret .= "$name: $usage\n";
- }
-
- throw new MWException( $ret );
- }
- }
-
- $id++;
- }
- }
-
- // Various getter functions
-
- /**
- * Get an input dictionary from a set of parser test files
- * @param array $filenames
- * @return string
- */
- function getFuzzInput( $filenames ) {
- $dict = '';
-
- foreach ( $filenames as $filename ) {
- $contents = file_get_contents( $filename );
- preg_match_all( '/!!\s*input\n(.*?)\n!!\s*result/s', $contents, $matches );
-
- foreach ( $matches[1] as $match ) {
- $dict .= $match . "\n";
- }
- }
-
- return $dict;
- }
-
- /**
- * Get a memory usage breakdown
- * @return array
- */
- function getMemoryBreakdown() {
- $memStats = [];
-
- foreach ( $GLOBALS as $name => $value ) {
- $memStats['$' . $name] = strlen( serialize( $value ) );
- }
-
- $classes = get_declared_classes();
-
- foreach ( $classes as $class ) {
- $rc = new ReflectionClass( $class );
- $props = $rc->getStaticProperties();
- $memStats[$class] = strlen( serialize( $props ) );
- $methods = $rc->getMethods();
-
- foreach ( $methods as $method ) {
- $memStats[$class] += strlen( serialize( $method->getStaticVariables() ) );
- }
- }
-
- $functions = get_defined_functions();
-
- foreach ( $functions['user'] as $function ) {
- $rf = new ReflectionFunction( $function );
- $memStats["$function()"] = strlen( serialize( $rf->getStaticVariables() ) );
- }
-
- asort( $memStats );
-
- return $memStats;
- }
-
- /**
- * Get a Parser object
- * @param Preprocessor $preprocessor
- * @return Parser
- */
- function getParser( $preprocessor = null ) {
- global $wgParserConf;
-
- $class = $wgParserConf['class'];
- $parser = new $class( [ 'preprocessorClass' => $preprocessor ] + $wgParserConf );
-
- Hooks::run( 'ParserTestParser', [ &$parser ] );
-
- return $parser;
- }
-
- // Various action functions
-
- public function addArticle( $name, $text, $line ) {
- self::$articles[$name] = [ $text, $line ];
- }
-
- public function publishTestArticles() {
- if ( empty( self::$articles ) ) {
- return;
- }
-
- foreach ( self::$articles as $name => $info ) {
- list( $text, $line ) = $info;
- ParserTest::addArticle( $name, $text, $line, 'ignoreduplicate' );
- }
- }
-
- /**
- * Steal a callback function from the primary parser, save it for
- * application to our scary parser. If the hook is not installed,
- * abort processing of this file.
- *
- * @param string $name
- * @return bool True if tag hook is present
- */
- public function requireHook( $name ) {
- global $wgParser;
- $wgParser->firstCallInit(); // make sure hooks are loaded.
- return isset( $wgParser->mTagHooks[$name] );
- }
-
- public function requireFunctionHook( $name ) {
- global $wgParser;
- $wgParser->firstCallInit(); // make sure hooks are loaded.
- return isset( $wgParser->mFunctionHooks[$name] );
- }
-
- public function requireTransparentHook( $name ) {
- global $wgParser;
- $wgParser->firstCallInit(); // make sure hooks are loaded.
- return isset( $wgParser->mTransparentTagHooks[$name] );
- }
-
- // Various "cleanup" functions
-
- /**
- * Remove last character if it is a newline
- * @param string $s
- * @return string
- */
- public function removeEndingNewline( $s ) {
- if ( substr( $s, -1 ) === "\n" ) {
- return substr( $s, 0, -1 );
- } else {
- return $s;
- }
- }
-
- // Test options parser functions
-
- protected function parseOptions( $instring ) {
- $opts = [];
- // foo
- // foo=bar
- // foo="bar baz"
- // foo=[[bar baz]]
- // foo=bar,"baz quux"
- $regex = '/\b
- ([\w-]+) # Key
- \b
- (?:\s*
- = # First sub-value
- \s*
- (
- "
- [^"]* # Quoted val
- "
- |
- \[\[
- [^]]* # Link target
- \]\]
- |
- [\w-]+ # Plain word
- )
- (?:\s*
- , # Sub-vals 1..N
- \s*
- (
- "[^"]*" # Quoted val
- |
- \[\[[^]]*\]\] # Link target
- |
- [\w-]+ # Plain word
- )
- )*
- )?
- /x';
-
- if ( preg_match_all( $regex, $instring, $matches, PREG_SET_ORDER ) ) {
- foreach ( $matches as $bits ) {
- array_shift( $bits );
- $key = strtolower( array_shift( $bits ) );
- if ( count( $bits ) == 0 ) {
- $opts[$key] = true;
- } elseif ( count( $bits ) == 1 ) {
- $opts[$key] = $this->cleanupOption( array_shift( $bits ) );
- } else {
- // Array!
- $opts[$key] = array_map( [ $this, 'cleanupOption' ], $bits );
- }
- }
- }
-
- return $opts;
- }
-
- protected function cleanupOption( $opt ) {
- if ( substr( $opt, 0, 1 ) == '"' ) {
- return substr( $opt, 1, -1 );
- }
-
- if ( substr( $opt, 0, 2 ) == '[[' ) {
- return substr( $opt, 2, -2 );
- }
-
- return $opt;
- }
-
- /**
- * Use a regex to find out the value of an option
- * @param string $key Name of option val to retrieve
- * @param array $opts Options array to look in
- * @param mixed $default Default value returned if not found
- * @return mixed
- */
- protected static function getOptionValue( $key, $opts, $default ) {
- $key = strtolower( $key );
-
- if ( isset( $opts[$key] ) ) {
- return $opts[$key];
- } else {
- return $default;
- }
- }
-}
--- /dev/null
+<?php
+
+/**
+ * This is the TestCase subclass for running a single parser test via the
+ * ParserTestRunner integration test system.
+ *
+ * Note: the following groups are not used by PHPUnit.
+ * The list in ParserTestFileSuite::__construct() is used instead.
+ *
+ * @group Database
+ * @group Parser
+ * @group ParserTests
+ *
+ * @todo covers tags
+ */
+class ParserIntegrationTest extends PHPUnit_Framework_TestCase {
+ /** @var array */
+ private $ptTest;
+
+ /** @var ParserTestRunner */
+ private $ptRunner;
+
+ /** @var ScopedCallback */
+ private $ptTeardownScope;
+
+ public function __construct( $runner, $fileName, $test ) {
+ parent::__construct( 'testParse', [ '[details omitted]' ],
+ basename( $fileName ) . ': ' . $test['desc'] );
+ $this->ptTest = $test;
+ $this->ptRunner = $runner;
+ }
+
+ public function testParse() {
+ $this->ptRunner->getRecorder()->setTestCase( $this );
+ $result = $this->ptRunner->runTest( $this->ptTest );
+ $this->assertEquals( $result->expected, $result->actual );
+ }
+
+ public function setUp() {
+ $this->ptTeardownScope = $this->ptRunner->staticSetup();
+ }
+
+ public function tearDown() {
+ if ( $this->ptTeardownScope ) {
+ ScopedCallback::consume( $this->ptTeardownScope );
+ }
+ }
+}
*/
public function testTemplateDependencies( $module, $expected ) {
$rl = new ResourceLoaderFileModule( $module );
+ $rl->setName( 'testing' );
$this->assertEquals( $rl->getDependencies(), $expected );
}
];
$module = new ResourceLoaderFileModule( $baseParams );
+ $module->setName( 'testing' );
$this->assertEquals(
[
'localBasePath' => $basePath,
'styles' => [ 'test.css' ],
] );
+ $testModule->setName( 'testing' );
$expectedModule = new ResourceLoaderFileModule( [
'localBasePath' => $basePath,
'styles' => [ 'expected.css' ],
] );
+ $expectedModule->setName( 'testing' );
$contextLtr = $this->getResourceLoaderContext( 'en', 'ltr' );
$contextRtl = $this->getResourceLoaderContext( 'he', 'rtl' );
*/
public function testGetTemplates( $module, $expected ) {
$rl = new ResourceLoaderFileModule( $module );
+ $rl->setName( 'testing' );
$this->assertEquals( $rl->getTemplates(), $expected );
}
'localBasePath' => $basePath,
'styles' => [ 'bom.css' ],
] );
+ $testModule->setName( 'testing' );
$this->assertEquals(
substr( file_get_contents( "$basePath/bom.css" ), 0, 10 ),
"\xef\xbb\xbf.efbbbf",
],
];
}
+
+ /**
+ * @covers ResourceLoaderWikiModule::getTitleInfo
+ */
+ public function testGetTitleInfo() {
+ $pages = [
+ 'MediaWiki:Common.css' => [ 'type' => 'styles' ],
+ 'mediawiki: fallback.css' => [ 'type' => 'styles' ],
+ ];
+ $titleInfo = [
+ 'MediaWiki:Common.css' => [ 'page_len' => 1234 ],
+ 'MediaWiki:Fallback.css' => [ 'page_len' => 0 ],
+ ];
+ $expected = $titleInfo;
+
+ $module = $this->getMockBuilder( 'TestResourceLoaderWikiModule' )
+ ->setMethods( [ 'getPages' ] )
+ ->getMock();
+ $module->method( 'getPages' )->willReturn( $pages );
+ // Can't mock static methods
+ $module::$returnFetchTitleInfo = $titleInfo;
+
+ $context = $this->getMockBuilder( 'ResourceLoaderContext' )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $module = TestingAccessWrapper::newFromObject( $module );
+ $this->assertEquals( $expected, $module->getTitleInfo( $context ), 'Title info' );
+ }
+
+ /**
+ * @covers ResourceLoaderWikiModule::getTitleInfo
+ * @covers ResourceLoaderWikiModule::setTitleInfo
+ * @covers ResourceLoaderWikiModule::preloadTitleInfo
+ */
+ public function testGetPreloadedTitleInfo() {
+ $pages = [
+ 'MediaWiki:Common.css' => [ 'type' => 'styles' ],
+ // Regression against T145673. It's impossible to statically declare page names in
+ // a canonical way since the canonical prefix is localised. As such, the preload
+ // cache computed the right cache key, but failed to find the results when
+ // doing an intersect on the canonical result, producing an empty array.
+ 'mediawiki: fallback.css' => [ 'type' => 'styles' ],
+ ];
+ $titleInfo = [
+ 'MediaWiki:Common.css' => [ 'page_len' => 1234 ],
+ 'MediaWiki:Fallback.css' => [ 'page_len' => 0 ],
+ ];
+ $expected = $titleInfo;
+
+ $module = $this->getMockBuilder( 'TestResourceLoaderWikiModule' )
+ ->setMethods( [ 'getPages' ] )
+ ->getMock();
+ $module->method( 'getPages' )->willReturn( $pages );
+ // Can't mock static methods
+ $module::$returnFetchTitleInfo = $titleInfo;
+
+ $rl = new EmptyResourceLoader();
+ $rl->register( 'testmodule', $module );
+ $context = new ResourceLoaderContext( $rl, new FauxRequest() );
+
+ TestResourceLoaderWikiModule::preloadTitleInfo(
+ $context,
+ wfGetDB( DB_REPLICA ),
+ [ 'testmodule' ]
+ );
+
+ $module = TestingAccessWrapper::newFromObject( $module );
+ $this->assertEquals( $expected, $module->getTitleInfo( $context ), 'Title info' );
+ }
+}
+
+class TestResourceLoaderWikiModule extends ResourceLoaderWikiModule {
+ public static $returnFetchTitleInfo = null;
+ protected static function fetchTitleInfo( IDatabase $db, array $pages, $fname = null ) {
+ $ret = self::$returnFetchTitleInfo;
+ self::$returnFetchTitleInfo = null;
+ return $ret;
+ }
}
* @todo give this test a real name explaining what is being tested here
*/
public function testBug29408() {
+ $this->setMwGlobals( 'wgUser', self::$users['uploader']->getUser() );
+
$repo = RepoGroup::singleton()->getLocalRepo();
- $stash = new UploadStash( $repo, self::$users['uploader']->getUser() );
+ $stash = new UploadStash( $repo );
// Throws exception caught by PHPUnit on failure
$file = $stash->stashFile( $this->bug29408File );
$this->assertNotNull( BotPassword::newFromCentralId( 43, 'BotPassword' ) );
}
+ /**
+ * @dataProvider provideCanonicalizeLoginData
+ */
+ public function testCanonicalizeLoginData( $username, $password, $expectedResult ) {
+ $result = BotPassword::canonicalizeLoginData( $username, $password );
+ if ( is_array( $expectedResult ) ) {
+ $this->assertArrayEquals( $expectedResult, $result, true, true );
+ } else {
+ $this->assertSame( $expectedResult, $result );
+ }
+ }
+
+ public function provideCanonicalizeLoginData() {
+ return [
+ [ 'user', 'pass', false ],
+ [ 'user', 'abc@def', false ],
+ [ 'legacy@user', 'pass', false ],
+ [ 'user@bot', '12345678901234567890123456789012',
+ [ 'user@bot', '12345678901234567890123456789012', true ] ],
+ [ 'user', 'bot@12345678901234567890123456789012',
+ [ 'user@bot', '12345678901234567890123456789012', true ] ],
+ [ 'user', 'bot@12345678901234567890123456789012345',
+ [ 'user@bot', '12345678901234567890123456789012345', true ] ],
+ [ 'user', 'bot@x@12345678901234567890123456789012',
+ [ 'user@bot@x', '12345678901234567890123456789012', true ] ],
+ ];
+ }
+
public function testLogin() {
// Test failure when bot passwords aren't enabled
$this->setMwGlobals( 'wgEnableBotPasswords', false );
// through this entry point or not.
define( 'MW_PHPUNIT_TEST', true );
-$wgPhpUnitClass = 'PHPUnit_TextUI_Command';
-
// Start up MediaWiki in command-line mode
require_once dirname( dirname( __DIR__ ) ) . "/maintenance/Maintenance.php";
class PHPUnitMaintClass extends Maintenance {
public static $additionalOptions = [
- 'regex' => false,
'file' => false,
'use-filebackend' => false,
'use-bagostuff' => false,
'use-jobqueue' => false,
- 'keep-uploads' => false,
'use-normal-tables' => false,
'reuse-db' => false,
'wiki' => false,
+ 'profiler' => false,
];
public function __construct() {
false, # not required
false # no arg needed
);
- $this->addOption(
- 'regex',
- 'Only run parser tests that match the given regex.',
- false,
- true
- );
$this->addOption( 'file', 'File describing parser tests.', false, true );
$this->addOption( 'use-filebackend', 'Use filebackend', false, true );
$this->addOption( 'use-bagostuff', 'Use bagostuff', false, true );
$this->addOption( 'use-jobqueue', 'Use jobqueue', false, true );
- $this->addOption(
- 'keep-uploads',
- 'Re-use the same upload directory for each test, don\'t delete it.',
- false,
- false
- );
$this->addOption( 'use-normal-tables', 'Use normal DB tables.', false, false );
$this->addOption(
'reuse-db', 'Init DB only if tables are missing and keep after finish.',
public function finalSetup() {
parent::finalSetup();
- global $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType, $wgMainWANCache;
- global $wgMainStash;
- global $wgLanguageConverterCacheType, $wgUseDatabaseMessages;
- global $wgLocaltimezone, $wgLocalisationCacheConf;
- global $wgDevelopmentWarnings;
- global $wgSessionProviders, $wgSessionPbkdf2Iterations;
- global $wgJobTypeConf;
- global $wgAuthManagerConfig, $wgAuth;
-
// Inject test autoloader
- require_once __DIR__ . '/../TestsAutoLoader.php';
-
- // wfWarn should cause tests to fail
- $wgDevelopmentWarnings = true;
-
- // Make sure all caches and stashes are either disabled or use
- // in-process cache only to prevent tests from using any preconfigured
- // cache meant for the local wiki from outside the test run.
- // See also MediaWikiTestCase::run() which mocks CACHE_DB and APC.
-
- // Disabled in DefaultSettings, override local settings
- $wgMainWANCache =
- $wgMainCacheType = CACHE_NONE;
- // Uses CACHE_ANYTHING in DefaultSettings, use hash instead of db
- $wgMessageCacheType =
- $wgParserCacheType =
- $wgSessionCacheType =
- $wgLanguageConverterCacheType = 'hash';
- // Uses db-replicated in DefaultSettings
- $wgMainStash = 'hash';
- // Use memory job queue
- $wgJobTypeConf = [
- 'default' => [ 'class' => 'JobQueueMemory', 'order' => 'fifo' ],
- ];
-
- $wgUseDatabaseMessages = false; # Set for future resets
-
- // Assume UTC for testing purposes
- $wgLocaltimezone = 'UTC';
-
- $wgLocalisationCacheConf['storeClass'] = 'LCStoreNull';
-
- // Generic MediaWiki\Session\SessionManager configuration for tests
- // We use CookieSessionProvider because things might be expecting
- // cookies to show up in a FauxRequest somewhere.
- $wgSessionProviders = [
- [
- 'class' => MediaWiki\Session\CookieSessionProvider::class,
- 'args' => [ [
- 'priority' => 30,
- 'callUserSetCookiesHook' => true,
- ] ],
- ],
- ];
-
- // Single-iteration PBKDF2 session secret derivation, for speed.
- $wgSessionPbkdf2Iterations = 1;
+ self::requireTestsAutoloader();
- // Generic AuthManager configuration for testing
- $wgAuthManagerConfig = [
- 'preauth' => [],
- 'primaryauth' => [
- [
- 'class' => MediaWiki\Auth\TemporaryPasswordPrimaryAuthenticationProvider::class,
- 'args' => [ [
- 'authoritative' => false,
- ] ],
- ],
- [
- 'class' => MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider::class,
- 'args' => [ [
- 'authoritative' => true,
- ] ],
- ],
- ],
- 'secondaryauth' => [],
- ];
- $wgAuth = new MediaWiki\Auth\AuthManagerAuthPlugin();
-
- // Bug 44192 Do not attempt to send a real e-mail
- Hooks::clear( 'AlternateUserMailer' );
- Hooks::register(
- 'AlternateUserMailer',
- function () {
- return false;
- }
- );
- // xdebug's default of 100 is too low for MediaWiki
- ini_set( 'xdebug.max_nesting_level', 1000 );
-
- // Bug T116683 serialize_precision of 100
- // may break testing against floating point values
- // treated with PHP's serialize()
- ini_set( 'serialize_precision', 17 );
-
- // TODO: we should call MediaWikiTestCase::prepareServices( new GlobalVarConfig() ) here.
- // But PHPUnit may not be loaded yet, so we have to wait until just
- // before PHPUnit_TextUI_Command::main() is executed at the end of this file.
+ TestSetup::applyInitialConfig();
}
public function execute() {
[ '--configuration', $IP . '/tests/phpunit/suite.xml' ] );
}
+ $phpUnitClass = 'PHPUnit_TextUI_Command';
+
if ( $this->hasOption( 'with-phpunitclass' ) ) {
- global $wgPhpUnitClass;
- $wgPhpUnitClass = $this->getOption( 'with-phpunitclass' );
+ $phpUnitClass = $this->getOption( 'with-phpunitclass' );
# Cleanup $args array so the option and its value do not
# pollute PHPUnit
}
}
+ if ( !class_exists( 'PHPUnit_Framework_TestCase' ) ) {
+ echo "PHPUnit not found. Please install it and other dev dependencies by
+ running `composer install` in MediaWiki root directory.\n";
+ exit( 1 );
+ }
+ if ( !class_exists( $phpUnitClass ) ) {
+ echo "PHPUnit entry point '" . $phpUnitClass . "' not found. Please make sure you installed
+ the containing component and check the spelling of the class name.\n";
+ exit( 1 );
+ }
+
+ echo defined( 'HHVM_VERSION' ) ?
+ 'Using HHVM ' . HHVM_VERSION . ' (' . PHP_VERSION . ")\n" :
+ 'Using PHP ' . PHP_VERSION . "\n";
+
+ // Prepare global services for unit tests.
+ MediaWikiTestCase::prepareServices( new GlobalVarConfig() );
+
+ $phpUnitClass::main();
}
public function getDbType() {
$maintClass = 'PHPUnitMaintClass';
require RUN_MAINTENANCE_IF_MAIN;
-
-if ( !class_exists( 'PHPUnit_Framework_TestCase' ) ) {
- echo "PHPUnit not found. Please install it and other dev dependencies by
-running `composer install` in MediaWiki root directory.\n";
- exit( 1 );
-}
-if ( !class_exists( $wgPhpUnitClass ) ) {
- echo "PHPUnit entry point '" . $wgPhpUnitClass . "' not found. Please make sure you installed
-the containing component and check the spelling of the class name.\n";
- exit( 1 );
-}
-
-echo defined( 'HHVM_VERSION' ) ?
- 'Using HHVM ' . HHVM_VERSION . ' (' . PHP_VERSION . ")\n" :
- 'Using PHP ' . PHP_VERSION . "\n";
-
-// Prepare global services for unit tests.
-// FIXME: this should be done in the finalSetup() method,
-// but PHPUnit may not have been loaded at that point.
-MediaWikiTestCase::prepareServices( new GlobalVarConfig() );
-
-$wgPhpUnitClass::main();
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+class ContentHandlerSanityTest extends MediaWikiTestCase {
+
+ public static function provideHandlers() {
+ $models = ContentHandler::getContentModels();
+ $handlers = [];
+ foreach ( $models as $model ) {
+ $handlers[] = [ ContentHandler::getForModelID( $model ) ];
+ }
+
+ return $handlers;
+ }
+
+ /**
+ * @dataProvider provideHandlers
+ * @param ContentHandler $handler
+ */
+ public function testMakeEmptyContent( ContentHandler $handler ) {
+ $content = $handler->makeEmptyContent();
+ $this->assertInstanceOf( Content::class, $content );
+ if ( $handler instanceof TextContentHandler ) {
+ // TextContentHandler::getContentClass() is protected, so bypass
+ // that restriction
+ $testingWrapper = TestingAccessWrapper::newFromObject( $handler );
+ $this->assertInstanceOf( $testingWrapper->getContentClass(), $content );
+ }
+
+ $handlerClass = get_class( $handler );
+ $contentClass = get_class( $content );
+
+ $this->assertTrue(
+ $content->isValid(),
+ "$handlerClass::makeEmptyContent() did not return a valid content ($contentClass::isValid())"
+ );
+ }
+}
<testsuites>
<testsuite name="includes">
<directory>includes</directory>
+ <!-- Parser tests must be invoked via their suite -->
+ <exclude>includes/parser/ParserIntegrationTest.php</exclude>
</testsuite>
<testsuite name="languages">
<directory>languages</directory>
</testsuite>
<testsuite name="parsertests">
- <file>includes/parser/MediaWikiParserTest.php</file>
+ <file>suites/CoreParserTestSuite.php</file>
<file>suites/ExtensionsParserTestSuite.php</file>
</testsuite>
<testsuite name="skins">
<exclude>
<group>Utility</group>
<group>Broken</group>
- <group>ParserFuzz</group>
<group>Stub</group>
</exclude>
</groups>
--- /dev/null
+<?php
+
+class CoreParserTestSuite extends PHPUnit_Framework_TestSuite {
+
+ public static function suite() {
+ return ParserTestTopLevelSuite::suite( ParserTestTopLevelSuite::CORE_ONLY );
+ }
+
+}
+
class ExtensionsParserTestSuite extends PHPUnit_Framework_TestSuite {
public static function suite() {
- return MediaWikiParserTest::suite( MediaWikiParserTest::NO_CORE );
+ return ParserTestTopLevelSuite::suite( ParserTestTopLevelSuite::NO_CORE );
}
}
--- /dev/null
+<?php
+
+/**
+ * This is the suite class for running tests within a single .txt source file.
+ * It is not invoked directly. Use --filter to select files, or
+ * use parserTests.php.
+ */
+class ParserTestFileSuite extends PHPUnit_Framework_TestSuite {
+ private $ptRunner;
+ private $ptFileName;
+ private $ptFileInfo;
+
+ function __construct( $runner, $name, $fileName ) {
+ parent::__construct( $name );
+ $this->ptRunner = $runner;
+ $this->ptFileName = $fileName;
+ $this->ptFileInfo = TestFileReader::read( $this->ptFileName );
+
+ foreach ( $this->ptFileInfo['tests'] as $test ) {
+ $this->addTest( new ParserIntegrationTest( $runner, $fileName, $test ),
+ [ 'Database', 'Parser', 'ParserTests' ] );
+ }
+ }
+
+ function setUp() {
+ $this->ptRunner->addArticles( $this->ptFileInfo[ 'articles'] );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * The UnitTest must be either a class that inherits from MediaWikiTestCase
+ * or a class that provides a public static suite() method which returns
+ * an PHPUnit_Framework_Test object
+ *
+ * @group Parser
+ * @group ParserTests
+ * @group Database
+ */
+class ParserTestTopLevelSuite extends PHPUnit_Framework_TestSuite {
+ /** @var ParserTestRunner */
+ private $ptRunner;
+
+ /** @var ScopedCallback */
+ private $ptTeardownScope;
+
+ /**
+ * @defgroup filtering_constants Filtering constants
+ *
+ * Limit inclusion of parser tests files coming from MediaWiki core
+ * @{
+ */
+
+ /** Include files shipped with MediaWiki core */
+ const CORE_ONLY = 1;
+ /** Include non core files as set in $wgParserTestFiles */
+ const NO_CORE = 2;
+ /** Include anything set via $wgParserTestFiles */
+ const WITH_ALL = 3; # CORE_ONLY | NO_CORE
+
+ /** @} */
+
+ /**
+ * Get a PHPUnit test suite of parser tests. Optionally filtered with
+ * $flags.
+ *
+ * @par Examples:
+ * Get a suite of parser tests shipped by MediaWiki core:
+ * @code
+ * ParserTestTopLevelSuite::suite( ParserTestTopLevelSuite::CORE_ONLY );
+ * @endcode
+ * Get a suite of various parser tests, like extensions:
+ * @code
+ * ParserTestTopLevelSuite::suite( ParserTestTopLevelSuite::NO_CORE );
+ * @endcode
+ * Get any test defined via $wgParserTestFiles:
+ * @code
+ * ParserTestTopLevelSuite::suite( ParserTestTopLevelSuite::WITH_ALL );
+ * @endcode
+ *
+ * @param int $flags Bitwise flag to filter out the $wgParserTestFiles that
+ * will be included. Default: ParserTestTopLevelSuite::CORE_ONLY
+ *
+ * @return PHPUnit_Framework_TestSuite
+ */
+ public static function suite( $flags = self::CORE_ONLY ) {
+ return new self( $flags );
+ }
+
+ function __construct( $flags ) {
+ parent::__construct();
+
+ $this->ptRecorder = new PhpunitTestRecorder;
+ $this->ptRunner = new ParserTestRunner( $this->ptRecorder );
+
+ if ( is_string( $flags ) ) {
+ $flags = self::CORE_ONLY;
+ }
+ global $wgParserTestFiles, $IP;
+
+ $mwTestDir = $IP . '/tests/';
+
+ # Human friendly helpers
+ $wantsCore = ( $flags & self::CORE_ONLY );
+ $wantsRest = ( $flags & self::NO_CORE );
+
+ # Will hold the .txt parser test files we will include
+ $filesToTest = [];
+
+ # Filter out .txt files
+ foreach ( $wgParserTestFiles as $parserTestFile ) {
+ $isCore = ( 0 === strpos( $parserTestFile, $mwTestDir ) );
+
+ if ( $isCore && $wantsCore ) {
+ self::debug( "included core parser tests: $parserTestFile" );
+ $filesToTest[] = $parserTestFile;
+ } elseif ( !$isCore && $wantsRest ) {
+ self::debug( "included non core parser tests: $parserTestFile" );
+ $filesToTest[] = $parserTestFile;
+ } else {
+ self::debug( "skipped parser tests: $parserTestFile" );
+ }
+ }
+ self::debug( 'parser tests files: '
+ . implode( ' ', $filesToTest ) );
+
+ $testList = [];
+ $counter = 0;
+ foreach ( $filesToTest as $fileName ) {
+ // Call the highest level directory the extension name.
+ // It may or may not actually be, but it should be close
+ // enough to cause there to be separate names for different
+ // things, which is good enough for our purposes.
+ $extensionName = basename( dirname( $fileName ) );
+ $testsName = $extensionName . '__' . basename( $fileName, '.txt' );
+ $parserTestClassName = ucfirst( $testsName );
+
+ // Official spec for class names: http://php.net/manual/en/language.oop5.basic.php
+ // Prepend 'ParserTest_' to be paranoid about it not starting with a number
+ $parserTestClassName = 'ParserTest_' .
+ preg_replace( '/[^a-zA-Z0-9_\x7f-\xff]/', '_', $parserTestClassName );
+
+ if ( isset( $testList[$parserTestClassName] ) ) {
+ // If there is a conflict, append a number.
+ $counter++;
+ $parserTestClassName .= $counter;
+ }
+ $testList[$parserTestClassName] = true;
+
+ // Previously we actually created a class here, with eval(). We now
+ // just override the name.
+
+ self::debug( "Adding test class $parserTestClassName" );
+ $this->addTest( new ParserTestFileSuite(
+ $this->ptRunner, $parserTestClassName, $fileName ) );
+ }
+ }
+
+ public function setUp() {
+ wfDebug( __METHOD__ );
+ $db = wfGetDB( DB_MASTER );
+ $type = $db->getType();
+ $prefix = $type === 'oracle' ?
+ MediaWikiTestCase::ORA_DB_PREFIX : MediaWikiTestCase::DB_PREFIX;
+ MediaWikiTestCase::setupTestDB( $db, $prefix );
+ $teardown = $this->ptRunner->setDatabase( $db );
+ $teardown = $this->ptRunner->setupUploads( $teardown );
+ $this->ptTeardownScope = $teardown;
+ }
+
+ public function tearDown() {
+ wfDebug( __METHOD__ );
+ if ( $this->ptTeardownScope ) {
+ ScopedCallback::consume( $this->ptTeardownScope );
+ }
+ }
+
+ /**
+ * Write $msg under log group 'tests-parser'
+ * @param string $msg Message to log
+ */
+ protected static function debug( $msg ) {
+ return wfDebugLog( 'tests-parser', wfGetCaller() . ' ' . $msg );
+ }
+}
+++ /dev/null
-<?php
-/**
- * Recording for passing/failing tests.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Testing
- */
-
-/**
- * Interface to record parser test results.
- *
- * The ITestRecorder is a very simple interface to record the result of
- * MediaWiki parser tests. One should call start() before running the
- * full parser tests and end() once all the tests have been finished.
- * After each test, you should use record() to keep track of your tests
- * results. Finally, report() is used to generate a summary of your
- * test run, one could dump it to the console for human consumption or
- * register the result in a database for tracking purposes.
- *
- * @since 1.22
- */
-interface ITestRecorder {
-
- /**
- * Called at beginning of the parser test run
- */
- public function start();
-
- /**
- * Called after each test
- * @param string $test
- * @param integer $subtest
- * @param bool $result
- */
- public function record( $test, $subtest, $result );
-
- /**
- * Called before finishing the test run
- */
- public function report();
-
- /**
- * Called at the end of the parser test run
- */
- public function end();
-
-}
-
-class TestRecorder implements ITestRecorder {
- public $parent;
- public $term;
-
- function __construct( $parent ) {
- $this->parent = $parent;
- $this->term = $parent->term;
- }
-
- function start() {
- $this->total = 0;
- $this->success = 0;
- }
-
- function record( $test, $subtest, $result ) {
- $this->total++;
- $this->success += ( $result ? 1 : 0 );
- }
-
- function end() {
- // dummy
- }
-
- function report() {
- if ( $this->total > 0 ) {
- $this->reportPercentage( $this->success, $this->total );
- } else {
- throw new MWException( "No tests found.\n" );
- }
- }
-
- function reportPercentage( $success, $total ) {
- $ratio = wfPercent( 100 * $success / $total );
- print $this->term->color( 1 ) . "Passed $success of $total tests ($ratio)... ";
-
- if ( $success == $total ) {
- print $this->term->color( 32 ) . "ALL TESTS PASSED!";
- } else {
- $failed = $total - $success;
- print $this->term->color( 31 ) . "$failed tests failed!";
- }
-
- print $this->term->reset() . "\n";
-
- return ( $success == $total );
- }
-}
-
-class DbTestPreviewer extends TestRecorder {
- protected $lb; // /< Database load balancer
- protected $db; // /< Database connection to the main DB
- protected $curRun; // /< run ID number for the current run
- protected $prevRun; // /< run ID number for the previous run, if any
- protected $results; // /< Result array
-
- /**
- * This should be called before the table prefix is changed
- * @param TestRecorder $parent
- */
- function __construct( $parent ) {
- parent::__construct( $parent );
-
- $this->lb = wfGetLBFactory()->newMainLB();
- // This connection will have the wiki's table prefix, not parsertest_
- $this->db = $this->lb->getConnection( DB_MASTER );
- }
-
- /**
- * Set up result recording; insert a record for the run with the date
- * and all that fun stuff
- */
- function start() {
- parent::start();
-
- if ( !$this->db->tableExists( 'testrun', __METHOD__ )
- || !$this->db->tableExists( 'testitem', __METHOD__ )
- ) {
- print "WARNING> `testrun` table not found in database.\n";
- $this->prevRun = false;
- } else {
- // We'll make comparisons against the previous run later...
- $this->prevRun = $this->db->selectField( 'testrun', 'MAX(tr_id)' );
- }
-
- $this->results = [];
- }
-
- function getName( $test, $subtest ) {
- if ( $subtest ) {
- return "$test subtest #$subtest";
- } else {
- return $test;
- }
- }
-
- function record( $test, $subtest, $result ) {
- parent::record( $test, $subtest, $result );
- $this->results[ $this->getName( $test, $subtest ) ] = $result;
- }
-
- function report() {
- if ( $this->prevRun ) {
- // f = fail, p = pass, n = nonexistent
- // codes show before then after
- $table = [
- 'fp' => 'previously failing test(s) now PASSING! :)',
- 'pn' => 'previously PASSING test(s) removed o_O',
- 'np' => 'new PASSING test(s) :)',
-
- 'pf' => 'previously passing test(s) now FAILING! :(',
- 'fn' => 'previously FAILING test(s) removed O_o',
- 'nf' => 'new FAILING test(s) :(',
- 'ff' => 'still FAILING test(s) :(',
- ];
-
- $prevResults = [];
-
- $res = $this->db->select( 'testitem', [ 'ti_name', 'ti_success' ],
- [ 'ti_run' => $this->prevRun ], __METHOD__ );
-
- foreach ( $res as $row ) {
- if ( !$this->parent->regex
- || preg_match( "/{$this->parent->regex}/i", $row->ti_name )
- ) {
- $prevResults[$row->ti_name] = $row->ti_success;
- }
- }
-
- $combined = array_keys( $this->results + $prevResults );
-
- # Determine breakdown by change type
- $breakdown = [];
- foreach ( $combined as $test ) {
- if ( !isset( $prevResults[$test] ) ) {
- $before = 'n';
- } elseif ( $prevResults[$test] == 1 ) {
- $before = 'p';
- } else /* if ( $prevResults[$test] == 0 )*/ {
- $before = 'f';
- }
-
- if ( !isset( $this->results[$test] ) ) {
- $after = 'n';
- } elseif ( $this->results[$test] == 1 ) {
- $after = 'p';
- } else /*if ( $this->results[$test] == 0 ) */ {
- $after = 'f';
- }
-
- $code = $before . $after;
-
- if ( isset( $table[$code] ) ) {
- $breakdown[$code][$test] = $this->getTestStatusInfo( $test, $after );
- }
- }
-
- # Write out results
- foreach ( $table as $code => $label ) {
- if ( !empty( $breakdown[$code] ) ) {
- $count = count( $breakdown[$code] );
- printf( "\n%4d %s\n", $count, $label );
-
- foreach ( $breakdown[$code] as $differing_test_name => $statusInfo ) {
- print " * $differing_test_name [$statusInfo]\n";
- }
- }
- }
- } else {
- print "No previous test runs to compare against.\n";
- }
-
- print "\n";
- parent::report();
- }
-
- /**
- * Returns a string giving information about when a test last had a status change.
- * Could help to track down when regressions were introduced, as distinct from tests
- * which have never passed (which are more change requests than regressions).
- * @param string $testname
- * @param string $after
- * @return string
- */
- private function getTestStatusInfo( $testname, $after ) {
- // If we're looking at a test that has just been removed, then say when it first appeared.
- if ( $after == 'n' ) {
- $changedRun = $this->db->selectField( 'testitem',
- 'MIN(ti_run)',
- [ 'ti_name' => $testname ],
- __METHOD__ );
- $appear = $this->db->selectRow( 'testrun',
- [ 'tr_date', 'tr_mw_version' ],
- [ 'tr_id' => $changedRun ],
- __METHOD__ );
-
- return "First recorded appearance: "
- . date( "d-M-Y H:i:s", strtotime( $appear->tr_date ) )
- . ", " . $appear->tr_mw_version;
- }
-
- // Otherwise, this test has previous recorded results.
- // See when this test last had a different result to what we're seeing now.
- $conds = [
- 'ti_name' => $testname,
- 'ti_success' => ( $after == 'f' ? "1" : "0" ) ];
-
- if ( $this->curRun ) {
- $conds[] = "ti_run != " . $this->db->addQuotes( $this->curRun );
- }
-
- $changedRun = $this->db->selectField( 'testitem', 'MAX(ti_run)', $conds, __METHOD__ );
-
- // If no record of ever having had a different result.
- if ( is_null( $changedRun ) ) {
- if ( $after == "f" ) {
- return "Has never passed";
- } else {
- return "Has never failed";
- }
- }
-
- // Otherwise, we're looking at a test whose status has changed.
- // (i.e. it used to work, but now doesn't; or used to fail, but is now fixed.)
- // In this situation, give as much info as we can as to when it changed status.
- $pre = $this->db->selectRow( 'testrun',
- [ 'tr_date', 'tr_mw_version' ],
- [ 'tr_id' => $changedRun ],
- __METHOD__ );
- $post = $this->db->selectRow( 'testrun',
- [ 'tr_date', 'tr_mw_version' ],
- [ "tr_id > " . $this->db->addQuotes( $changedRun ) ],
- __METHOD__,
- [ "LIMIT" => 1, "ORDER BY" => 'tr_id' ]
- );
-
- if ( $post ) {
- $postDate = date( "d-M-Y H:i:s", strtotime( $post->tr_date ) ) . ", {$post->tr_mw_version}";
- } else {
- $postDate = 'now';
- }
-
- return ( $after == "f" ? "Introduced" : "Fixed" ) . " between "
- . date( "d-M-Y H:i:s", strtotime( $pre->tr_date ) ) . ", " . $pre->tr_mw_version
- . " and $postDate";
- }
-
- /**
- * Close the DB connection
- */
- function end() {
- $this->lb->closeAll();
- parent::end();
- }
-}
-
-class DbTestRecorder extends DbTestPreviewer {
- public $version;
-
- /**
- * Set up result recording; insert a record for the run with the date
- * and all that fun stuff
- */
- function start() {
- $this->db->begin( __METHOD__ );
-
- if ( !$this->db->tableExists( 'testrun' )
- || !$this->db->tableExists( 'testitem' )
- ) {
- print "WARNING> `testrun` table not found in database. Trying to create table.\n";
- $this->db->sourceFile( $this->db->patchPath( 'patch-testrun.sql' ) );
- echo "OK, resuming.\n";
- }
-
- parent::start();
-
- $this->db->insert( 'testrun',
- [
- 'tr_date' => $this->db->timestamp(),
- 'tr_mw_version' => $this->version,
- 'tr_php_version' => PHP_VERSION,
- 'tr_db_version' => $this->db->getServerVersion(),
- 'tr_uname' => php_uname()
- ],
- __METHOD__ );
- if ( $this->db->getType() === 'postgres' ) {
- $this->curRun = $this->db->currentSequenceValue( 'testrun_id_seq' );
- } else {
- $this->curRun = $this->db->insertId();
- }
- }
-
- /**
- * Record an individual test item's success or failure to the db
- *
- * @param string $test
- * @param bool $result
- */
- function record( $test, $subtest, $result ) {
- parent::record( $test, $subtest, $result );
-
- $this->db->insert( 'testitem',
- [
- 'ti_run' => $this->curRun,
- 'ti_name' => $this->getName( $test, $subtest ),
- 'ti_success' => $result ? 1 : 0,
- ],
- __METHOD__ );
- }
-
- /**
- * Commit transaction and clean up for result recording
- */
- function end() {
- $this->db->commit( __METHOD__ );
- parent::end();
- }
-}
-
-class TestFileIterator implements Iterator {
- private $file;
- private $fh;
- /**
- * @var ParserTest|MediaWikiParserTest An instance of ParserTest (parserTests.php)
- * or MediaWikiParserTest (phpunit)
- */
- private $parserTest;
- private $index = 0;
- private $test;
- private $section = null;
- /** String|null: current test section being analyzed */
- private $sectionData = [];
- private $lineNum;
- private $eof;
- # Create a fake parser tests which never run anything unless
- # asked to do so. This will avoid running hooks for a disabled test
- private $delayedParserTest;
- private $nextSubTest = 0;
-
- function __construct( $file, $parserTest ) {
- $this->file = $file;
- $this->fh = fopen( $this->file, "rt" );
-
- if ( !$this->fh ) {
- throw new MWException( "Couldn't open file '$file'\n" );
- }
-
- $this->parserTest = $parserTest;
- $this->delayedParserTest = new DelayedParserTest();
-
- $this->lineNum = $this->index = 0;
- }
-
- function rewind() {
- if ( fseek( $this->fh, 0 ) ) {
- throw new MWException( "Couldn't fseek to the start of '$this->file'\n" );
- }
-
- $this->index = -1;
- $this->lineNum = 0;
- $this->eof = false;
- $this->next();
-
- return true;
- }
-
- function current() {
- return $this->test;
- }
-
- function key() {
- return $this->index;
- }
-
- function next() {
- if ( $this->readNextTest() ) {
- $this->index++;
- return true;
- } else {
- $this->eof = true;
- }
- }
-
- function valid() {
- return $this->eof != true;
- }
-
- function setupCurrentTest() {
- // "input" and "result" are old section names allowed
- // for backwards-compatibility.
- $input = $this->checkSection( [ 'wikitext', 'input' ], false );
- $result = $this->checkSection( [ 'html/php', 'html/*', 'html', 'result' ], false );
- // some tests have "with tidy" and "without tidy" variants
- $tidy = $this->checkSection( [ 'html/php+tidy', 'html+tidy' ], false );
- if ( $tidy != false ) {
- if ( $this->nextSubTest == 0 ) {
- if ( $result != false ) {
- $this->nextSubTest = 1; // rerun non-tidy variant later
- }
- $result = $tidy;
- } else {
- $this->nextSubTest = 0; // go on to next test after this
- $tidy = false;
- }
- }
-
- if ( !isset( $this->sectionData['options'] ) ) {
- $this->sectionData['options'] = '';
- }
-
- if ( !isset( $this->sectionData['config'] ) ) {
- $this->sectionData['config'] = '';
- }
-
- $isDisabled = preg_match( '/\\bdisabled\\b/i', $this->sectionData['options'] ) &&
- !$this->parserTest->runDisabled;
- $isParsoidOnly = preg_match( '/\\bparsoid\\b/i', $this->sectionData['options'] ) &&
- $result == 'html' &&
- !$this->parserTest->runParsoid;
- $isFiltered = !preg_match( "/" . $this->parserTest->regex . "/i", $this->sectionData['test'] );
- if ( $input == false || $result == false || $isDisabled || $isParsoidOnly || $isFiltered ) {
- # disabled test
- return false;
- }
-
- # We are really going to run the test, run pending hooks and hooks function
- wfDebug( __METHOD__ . " unleashing delayed test for: {$this->sectionData['test']}" );
- $hooksResult = $this->delayedParserTest->unleash( $this->parserTest );
- if ( !$hooksResult ) {
- # Some hook reported an issue. Abort.
- throw new MWException( "Problem running requested parser hook from the test file" );
- }
-
- $this->test = [
- 'test' => ParserTest::chomp( $this->sectionData['test'] ),
- 'subtest' => $this->nextSubTest,
- 'input' => ParserTest::chomp( $this->sectionData[$input] ),
- 'result' => ParserTest::chomp( $this->sectionData[$result] ),
- 'options' => ParserTest::chomp( $this->sectionData['options'] ),
- 'config' => ParserTest::chomp( $this->sectionData['config'] ),
- ];
- if ( $tidy != false ) {
- $this->test['options'] .= " tidy";
- }
- return true;
- }
-
- function readNextTest() {
- # Run additional subtests of previous test
- while ( $this->nextSubTest > 0 ) {
- if ( $this->setupCurrentTest() ) {
- return true;
- }
- }
-
- $this->clearSection();
- # Reset hooks for the delayed test object
- $this->delayedParserTest->reset();
-
- while ( false !== ( $line = fgets( $this->fh ) ) ) {
- $this->lineNum++;
- $matches = [];
-
- if ( preg_match( '/^!!\s*(\S+)/', $line, $matches ) ) {
- $this->section = strtolower( $matches[1] );
-
- if ( $this->section == 'endarticle' ) {
- $this->checkSection( 'text' );
- $this->checkSection( 'article' );
-
- $this->parserTest->addArticle(
- ParserTest::chomp( $this->sectionData['article'] ),
- $this->sectionData['text'], $this->lineNum );
-
- $this->clearSection();
-
- continue;
- }
-
- if ( $this->section == 'endhooks' ) {
- $this->checkSection( 'hooks' );
-
- foreach ( explode( "\n", $this->sectionData['hooks'] ) as $line ) {
- $line = trim( $line );
-
- if ( $line ) {
- $this->delayedParserTest->requireHook( $line );
- }
- }
-
- $this->clearSection();
-
- continue;
- }
-
- if ( $this->section == 'endfunctionhooks' ) {
- $this->checkSection( 'functionhooks' );
-
- foreach ( explode( "\n", $this->sectionData['functionhooks'] ) as $line ) {
- $line = trim( $line );
-
- if ( $line ) {
- $this->delayedParserTest->requireFunctionHook( $line );
- }
- }
-
- $this->clearSection();
-
- continue;
- }
-
- if ( $this->section == 'endtransparenthooks' ) {
- $this->checkSection( 'transparenthooks' );
-
- foreach ( explode( "\n", $this->sectionData['transparenthooks'] ) as $line ) {
- $line = trim( $line );
-
- if ( $line ) {
- $this->delayedParserTest->requireTransparentHook( $line );
- }
- }
-
- $this->clearSection();
-
- continue;
- }
-
- if ( $this->section == 'end' ) {
- $this->checkSection( 'test' );
- do {
- if ( $this->setupCurrentTest() ) {
- return true;
- }
- } while ( $this->nextSubTest > 0 );
- # go on to next test (since this was disabled)
- $this->clearSection();
- $this->delayedParserTest->reset();
- continue;
- }
-
- if ( isset( $this->sectionData[$this->section] ) ) {
- throw new MWException( "duplicate section '$this->section' "
- . "at line {$this->lineNum} of $this->file\n" );
- }
-
- $this->sectionData[$this->section] = '';
-
- continue;
- }
-
- if ( $this->section ) {
- $this->sectionData[$this->section] .= $line;
- }
- }
-
- return false;
- }
-
- /**
- * Clear section name and its data
- */
- private function clearSection() {
- $this->sectionData = [];
- $this->section = null;
-
- }
-
- /**
- * Verify the current section data has some value for the given token
- * name(s) (first parameter).
- * Throw an exception if it is not set, referencing current section
- * and adding the current file name and line number
- *
- * @param string|array $tokens Expected token(s) that should have been
- * mentioned before closing this section
- * @param bool $fatal True iff an exception should be thrown if
- * the section is not found.
- * @return bool|string
- * @throws MWException
- */
- private function checkSection( $tokens, $fatal = true ) {
- if ( is_null( $this->section ) ) {
- throw new MWException( __METHOD__ . " can not verify a null section!\n" );
- }
- if ( !is_array( $tokens ) ) {
- $tokens = [ $tokens ];
- }
- if ( count( $tokens ) == 0 ) {
- throw new MWException( __METHOD__ . " can not verify zero sections!\n" );
- }
-
- $data = $this->sectionData;
- $tokens = array_filter( $tokens, function ( $token ) use ( $data ) {
- return isset( $data[$token] );
- } );
-
- if ( count( $tokens ) == 0 ) {
- if ( !$fatal ) {
- return false;
- }
- throw new MWException( sprintf(
- "'%s' without '%s' at line %s of %s\n",
- $this->section,
- implode( ',', $tokens ),
- $this->lineNum,
- $this->file
- ) );
- }
- if ( count( $tokens ) > 1 ) {
- throw new MWException( sprintf(
- "'%s' with unexpected tokens '%s' at line %s of %s\n",
- $this->section,
- implode( ',', $tokens ),
- $this->lineNum,
- $this->file
- ) );
- }
-
- return array_values( $tokens )[0];
- }
-}
-
-/**
- * An iterator for use as a phpunit data provider. Provides the test arguments
- * in the order expected by NewParserTest::testParserTest().
- */
-class TestFileDataProvider extends TestFileIterator {
- function current() {
- $test = parent::current();
- if ( $test ) {
- return [
- $test['test'],
- $test['input'],
- $test['result'],
- $test['options'],
- $test['config'],
- ];
- } else {
- return $test;
- }
- }
-}
-
-/**
- * A class to delay execution of a parser test hooks.
- */
-class DelayedParserTest {
-
- /** Initialized on construction */
- private $hooks;
- private $fnHooks;
- private $transparentHooks;
-
- public function __construct() {
- $this->reset();
- }
-
- /**
- * Init/reset or forgot about the current delayed test.
- * Call to this will erase any hooks function that were pending.
- */
- public function reset() {
- $this->hooks = [];
- $this->fnHooks = [];
- $this->transparentHooks = [];
- }
-
- /**
- * Called whenever we actually want to run the hook.
- * Should be the case if we found the parserTest is not disabled
- * @param ParserTest|NewParserTest $parserTest
- * @return bool
- * @throws MWException
- */
- public function unleash( &$parserTest ) {
- if ( !( $parserTest instanceof ParserTest || $parserTest instanceof NewParserTest ) ) {
- throw new MWException( __METHOD__ . " must be passed an instance of ParserTest or "
- . "NewParserTest classes\n" );
- }
-
- # Trigger delayed hooks. Any failure will make us abort
- foreach ( $this->hooks as $hook ) {
- $ret = $parserTest->requireHook( $hook );
- if ( !$ret ) {
- return false;
- }
- }
-
- # Trigger delayed function hooks. Any failure will make us abort
- foreach ( $this->fnHooks as $fnHook ) {
- $ret = $parserTest->requireFunctionHook( $fnHook );
- if ( !$ret ) {
- return false;
- }
- }
-
- # Trigger delayed transparent hooks. Any failure will make us abort
- foreach ( $this->transparentHooks as $hook ) {
- $ret = $parserTest->requireTransparentHook( $hook );
- if ( !$ret ) {
- return false;
- }
- }
-
- # Delayed execution was successful.
- return true;
- }
-
- /**
- * Similar to ParserTest object but does not run anything
- * Use unleash() to really execute the hook
- * @param string $hook
- */
- public function requireHook( $hook ) {
- $this->hooks[] = $hook;
- }
-
- /**
- * Similar to ParserTest object but does not run anything
- * Use unleash() to really execute the hook function
- * @param string $fnHook
- */
- public function requireFunctionHook( $fnHook ) {
- $this->fnHooks[] = $fnHook;
- }
-
- /**
- * Similar to ParserTest object but does not run anything
- * Use unleash() to really execute the hook function
- * @param string $hook
- */
- public function requireTransparentHook( $hook ) {
- $this->transparentHooks[] = $hook;
- }
-
-}
-
-/**
- * Initialize and detect the DjVu files support
- */
-class DjVuSupport {
-
- /**
- * Initialises DjVu tools global with default values
- */
- public function __construct() {
- global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML, $wgFileExtensions, $wgDjvuTxt;
-
- $wgDjvuRenderer = $wgDjvuRenderer ? $wgDjvuRenderer : '/usr/bin/ddjvu';
- $wgDjvuDump = $wgDjvuDump ? $wgDjvuDump : '/usr/bin/djvudump';
- $wgDjvuToXML = $wgDjvuToXML ? $wgDjvuToXML : '/usr/bin/djvutoxml';
- $wgDjvuTxt = $wgDjvuTxt ? $wgDjvuTxt : '/usr/bin/djvutxt';
-
- if ( !in_array( 'djvu', $wgFileExtensions ) ) {
- $wgFileExtensions[] = 'djvu';
- }
- }
-
- /**
- * Returns true if the DjVu tools are usable
- *
- * @return bool
- */
- public function isEnabled() {
- global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML, $wgDjvuTxt;
-
- return is_executable( $wgDjvuRenderer )
- && is_executable( $wgDjvuDump )
- && is_executable( $wgDjvuToXML )
- && is_executable( $wgDjvuTxt );
- }
-}
-
-/**
- * Initialize and detect the tidy support
- */
-class TidySupport {
- private $enabled;
- private $config;
-
- /**
- * Determine if there is a usable tidy.
- */
- public function __construct( $useConfiguration = false ) {
- global $IP, $wgUseTidy, $wgTidyBin, $wgTidyInternal, $wgTidyConfig,
- $wgTidyConf, $wgTidyOpts;
-
- $this->enabled = true;
- if ( $useConfiguration ) {
- if ( $wgTidyConfig !== null ) {
- $this->config = $wgTidyConfig;
- } elseif ( $wgUseTidy ) {
- $this->config = [
- 'tidyConfigFile' => $wgTidyConf,
- 'debugComment' => false,
- 'tidyBin' => $wgTidyBin,
- 'tidyCommandLine' => $wgTidyOpts
- ];
- if ( $wgTidyInternal ) {
- $this->config['driver'] = wfIsHHVM() ? 'RaggettInternalHHVM' : 'RaggettInternalPHP';
- } else {
- $this->config['driver'] = 'RaggettExternal';
- }
- } else {
- $this->enabled = false;
- }
- } else {
- $this->config = [
- 'tidyConfigFile' => "$IP/includes/tidy/tidy.conf",
- 'tidyCommandLine' => '',
- ];
- if ( extension_loaded( 'tidy' ) && class_exists( 'tidy' ) ) {
- $this->config['driver'] = wfIsHHVM() ? 'RaggettInternalHHVM' : 'RaggettInternalPHP';
- } else {
- if ( is_executable( $wgTidyBin ) ) {
- $this->config['driver'] = 'RaggettExternal';
- $this->config['tidyBin'] = $wgTidyBin;
- } else {
- $path = Installer::locateExecutableInDefaultPaths( $wgTidyBin );
- if ( $path !== false ) {
- $this->config['driver'] = 'RaggettExternal';
- $this->config['tidyBin'] = $wgTidyBin;
- } else {
- $this->enabled = false;
- }
- }
- }
- }
- if ( !$this->enabled ) {
- $this->config = [ 'driver' => 'disabled' ];
- }
- }
-
- /**
- * Returns true if tidy is usable
- *
- * @return bool
- */
- public function isEnabled() {
- return $this->enabled;
- }
-
- public function getConfig() {
- return $this->config;
- }
-}