--dbuser "$dbuser"
--dbpass ""
--scriptpath "/w"
+ - echo -en "\n\nrequire_once __DIR__ . '/includes/DevelopmentSettings.php';\n" >> ./LocalSettings.php
+ - php -l ./LocalSettings.php
script:
- php tests/phpunit/phpunit.php
chromium: {
browsers: [ 'Chromium' ]
},
- more: {
- browsers: [ 'Chrome', 'Firefox' ]
+ firefox: {
+ browsers: [ 'Firefox' ]
}
},
copy: {
default mode.
* CACHE_ACCEL now only supports APC(u) or WinCache. XCache support was removed
as upstream is inactive and has no plans to move to PHP 7.
+* The old CategorizedRecentChanges feature, including its related configuration
+ option $wgAllowCategorizedRecentChanges, has been removed.
=== New features in 1.31 ===
* Wikimedia\Rdbms\IDatabase->select() and similar methods now support
* Updated wikimedia/running-stat from 1.1.0 to 1.2.0.
* Updated wikimedia/wrappedstring from 2.2.0 to 2.3.0.
* Updated mediawiki/at-ease from 1.1.0 to 1.2.0.
+* Updated wikimedia/php-session-serializer from 1.0.4 to 1.0.5.
* …
==== New external libraries ====
* CommentStore::getCommentLegacy
* CommentStore::insert
* CommentStore::insertWithTemplate
+* The method ResourceLoaderModule::getPosition(), deprecated in 1.29, has been removed.
== Compatibility ==
MediaWiki 1.31 requires PHP 5.5.9 or later. Although HHVM 3.18.5 or later is supported,
'UserBlockedError' => __DIR__ . '/includes/exception/UserBlockedError.php',
'UserCache' => __DIR__ . '/includes/cache/UserCache.php',
'UserDupes' => __DIR__ . '/maintenance/userDupes.inc',
+ 'UserGroupExpiryJob' => __DIR__ . '/includes/jobqueue/jobs/UserGroupExpiryJob.php',
'UserGroupMembership' => __DIR__ . '/includes/user/UserGroupMembership.php',
'UserMailer' => __DIR__ . '/includes/mail/UserMailer.php',
'UserNamePrefixSearch' => __DIR__ . '/includes/user/UserNamePrefixSearch.php',
"wikimedia/html-formatter": "1.0.1",
"wikimedia/ip-set": "1.2.0",
"wikimedia/object-factory": "1.0.0",
- "wikimedia/php-session-serializer": "1.0.4",
+ "wikimedia/php-session-serializer": "1.0.5",
"wikimedia/purtle": "1.0.6",
"wikimedia/relpath": "2.1.1",
"wikimedia/remex-html": "1.0.2",
# Generate the article HTML as if viewed by a web request
$article = new Article( Title::newFromText( $t ) );
$article->view();
-
+
versus
# Save current globals
* $a = $cf->run();
* print implode( ',' , $a );
* @endcode
+ *
+ * @deprecated since 1.31
*/
class CategoryFinder {
/** @var int[] The original article IDs passed to the seed function */
*/
$wgGitRepositoryViewers = [
'https://(?:[a-z0-9_]+@)?gerrit.wikimedia.org/r/(?:p/)?(.*)' =>
- 'https://phabricator.wikimedia.org/r/revision/%R;%H',
+ 'https://gerrit.wikimedia.org/g/%R/+/%H',
'ssh://(?:[a-z0-9_]+@)?gerrit.wikimedia.org:29418/(.*)' =>
- 'https://phabricator.wikimedia.org/r/revision/%R;%H',
+ 'https://gerrit.wikimedia.org/g/%R/+/%H',
];
/** @} */ # End of maintenance }
*/
$wgDisableAnonTalk = false;
-/**
- * Enable filtering of categories in Recentchanges
- */
-$wgAllowCategorizedRecentChanges = false;
-
/**
* Allow filtering by change tag in recentchanges, history, etc
* Has no effect if no tags are defined in valid_tag.
'clearUserWatchlist' => ClearUserWatchlistJob::class,
'cdnPurge' => CdnPurgeJob::class,
'enqueue' => EnqueueJob::class, // local queue for multi-DC setups
+ 'userGroupExpiry' => UserGroupExpiryJob::class,
'null' => NullJob::class,
];
--- /dev/null
+<?php
+/**
+ * Extra settings useful for MediaWiki development.
+ *
+ * To enable built-in debug and development settings, add the
+ * following to your LocalSettings.php file.
+ *
+ * require "$IP/includes/DevelopmentSettings.php";
+ *
+ * Alternatively, if running phpunit.php (or another Maintenance script),
+ * you can use the --mwdebug option to automatically load these settings.
+ *
+ * @file
+ */
+
+/**
+ * Debugging: PHP
+ */
+
+// Enable showing of errors
+error_reporting( -1 );
+ini_set( 'display_errors', 1 );
+
+/**
+ * Debugging: MediaWiki
+ */
+global $wgDevelopmentWarnings, $wgShowDBErrorBacktrace, $wgShowExceptionDetails,
+ $wgShowSQLErrors, $wgDebugRawPage,
+ $wgDebugComments, $wgDebugDumpSql, $wgDebugTimestamps,
+ $wgCommandLineMode, $wgDebugLogFile, $wgDBerrorLog, $wgDebugLogGroups;
+
+// Use of wfWarn() should cause tests to fail
+$wgDevelopmentWarnings = true;
+
+// Enable showing of errors
+$wgShowDBErrorBacktrace = true;
+$wgShowExceptionDetails = true;
+$wgShowSQLErrors = true;
+$wgDebugRawPage = true; // T49960
+
+// Enable verbose logging
+$wgDebugComments = true;
+$wgDebugDumpSql = true;
+$wgDebugTimestamps = true;
+
+// Enable log files
+$logDir = getenv( 'MW_LOG_DIR' );
+if ( $logDir ) {
+ if ( $wgCommandLineMode ) {
+ $wgDebugLogFile = "$logDir/mw-debug-cli.log";
+ } else {
+ $wgDebugLogFile = "$logDir/mw-debug-www.log";
+ }
+ $wgDBerrorLog = "$logDir/mw-dberror.log";
+ $wgDebugLogGroups['ratelimit'] = "$logDir/mw-ratelimit.log";
+ $wgDebugLogGroups['exception'] = "$logDir/mw-exception.log";
+ $wgDebugLogGroups['error'] = "$logDir/mw-error.log";
+}
+unset( $logDir );
/** @var bool Has a summary been preset using GET parameter &summary= ? */
public $hasPresetSummary = false;
- /** @var Revision|bool */
+ /** @var Revision|bool|null */
public $mBaseRevision = false;
/** @var bool */
/**
* @note: this method is very poorly named. If the user opened the form with ?oldid=X,
* one might think of X as the "base revision", which is NOT what this returns.
- * @return Revision Current version when the edit was started
+ * @return Revision|null Current version when the edit was started
*/
public function getBaseRevision() {
if ( !$this->mBaseRevision ) {
$this->mTitle, $pstContent, $user );
$parserOutput = $pstContent->getParserOutput( $this->mTitle, null, $parserOptions );
ScopedCallback::consume( $scopedCallback );
- $parserOutput->setEditSectionTokens( false ); // no section edit links
return [
'parserOutput' => $parserOutput,
'html' => $parserOutput->getText( [
*/
private $mEnableTOC = false;
- /**
- * @var bool Whether parser output should contain section edit links
- */
- private $mEnableSectionEditLinks = true;
-
/**
* @var string|null The URL to send in a <link> element with rel=license
*/
* Filter an array of modules to remove insufficiently trustworthy members, and modules
* which are no longer registered (eg a page is cached before an extension is disabled)
* @param array $modules
- * @param string|null $position If not null, only return modules with this position
+ * @param string|null $position Unused
* @param string $type
* @return array
*/
$module = $resourceLoader->getModule( $val );
if ( $module instanceof ResourceLoaderModule
&& $module->getOrigin() <= $this->getAllowedModules( $type )
- && ( is_null( $position ) || $module->getPosition() == $position )
) {
if ( $this->mTarget && !in_array( $this->mTarget, $module->getTargets() ) ) {
$this->warnModuleTargetFilter( $module->getName() );
* Get the list of modules to include on this page
*
* @param bool $filter Whether to filter out insufficiently trustworthy modules
- * @param string|null $position If not null, only return modules with this position
+ * @param string|null $position Unused
* @param string $param
* @param string $type
* @return array Array of module names
) {
$modules = array_values( array_unique( $this->$param ) );
return $filter
- ? $this->filterModules( $modules, $position, $type )
+ ? $this->filterModules( $modules, null, $type )
: $modules;
}
* Get the list of module JS to include on this page
*
* @param bool $filter
- * @param string|null $position
+ * @param string|null $position Unused
* @return array Array of module names
*/
public function getModuleScripts( $filter = false, $position = null ) {
- return $this->getModules( $filter, $position, 'mModuleScripts',
+ return $this->getModules( $filter, null, 'mModuleScripts',
ResourceLoaderModule::TYPE_SCRIPTS
);
}
* Get the list of module CSS to include on this page
*
* @param bool $filter
- * @param string|null $position
+ * @param string|null $position Unused
* @return array Array of module names
*/
public function getModuleStyles( $filter = false, $position = null ) {
- return $this->getModules( $filter, $position, 'mModuleStyles',
+ return $this->getModules( $filter, null, 'mModuleStyles',
ResourceLoaderModule::TYPE_STYLES
);
}
// Someone is trying to set a bogus pre-$wgUser PO. Check if it has
// been changed somehow, and keep it if so.
$anonPO = ParserOptions::newFromAnon();
- $anonPO->setEditSection( false );
$anonPO->setAllowUnsafeRawHtml( false );
if ( !$options->matches( $anonPO ) ) {
wfLogWarning( __METHOD__ . ': Setting a changed bogus ParserOptions: ' . wfGetAllCallers( 5 ) );
// ParserOptions for it. And don't cache this ParserOptions
// either.
$po = ParserOptions::newFromAnon();
- $po->setEditSection( false );
$po->setAllowUnsafeRawHtml( false );
$po->isBogus = true;
if ( $options !== null ) {
}
$this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
- $this->mParserOptions->setEditSection( false );
$this->mParserOptions->setAllowUnsafeRawHtml( false );
}
// so that extensions may modify ParserOutput to toggle TOC.
// This cannot be moved to addParserOutputText because that is not
// called by EditPage for Preview.
- if ( $parserOutput->getTOCEnabled() && $parserOutput->getTOCHTML() ) {
+ if ( $parserOutput->getTOCHTML() ) {
$this->mEnableTOC = true;
}
}
*/
function addParserOutput( $parserOutput, $poOptions = [] ) {
$this->addParserOutputMetadata( $parserOutput );
-
- // Touch section edit links only if not previously disabled
- if ( $parserOutput->getEditSectionTokens() ) {
- $parserOutput->setEditSectionTokens( $this->mEnableSectionEditLinks );
- }
- if ( !$this->mEnableSectionEditLinks
- && !array_key_exists( 'enableSectionEditLinks', $poOptions )
- ) {
- $poOptions['enableSectionEditLinks'] = false;
- }
-
$this->addParserOutputText( $parserOutput, $poOptions );
}
}
/**
- * JS stuff to put at the bottom of the `<body>`. These are modules with position 'bottom',
- * legacy scripts ($this->mScripts), and user JS.
+ * JS stuff to put at the bottom of the `<body>`.
+ * These are legacy scripts ($this->mScripts), and user JS.
*
* @return string|WrappedStringList HTML
*/
* @deprecated since 1.31, use $poOptions to addParserOutput() instead.
*/
public function enableSectionEditLinks( $flag = true ) {
- $this->mEnableSectionEditLinks = $flag;
+ wfDeprecated( __METHOD__, '1.31' );
}
/**
* @deprecated since 1.31, use $poOptions to addParserOutput() instead.
*/
public function sectionEditLinksEnabled() {
- return $this->mEnableSectionEditLinks;
+ wfDeprecated( __METHOD__, '1.31' );
+ return true;
}
/**
return $this->mRecord->getContent( 'main', $audience, $user );
}
catch ( RevisionAccessException $e ) {
+ wfDebugLog(
+ 'T184670',
+ __METHOD__ . ": Cannot get content: " . $e->getMessage() .
+ "\n" . $e->getTraceAsString()
+ );
return null;
}
}
wfDebug( __METHOD__ . ": reading site_stats from replica DB\n" );
$row = self::doLoadFromDB( $dbr );
- if ( !self::isSane( $row ) && $lb->hasOrMadeRecentMasterChanges() ) {
+ if ( !self::isRowSane( $row ) && $lb->hasOrMadeRecentMasterChanges() ) {
// Might have just been initialized during this request? Underflow?
wfDebug( __METHOD__ . ": site_stats damaged or missing on replica DB\n" );
$row = self::doLoadFromDB( $lb->getConnection( DB_MASTER ) );
}
- if ( !self::isSane( $row ) ) {
+ if ( !self::isRowSane( $row ) ) {
if ( $config->get( 'MiserMode' ) ) {
// Start off with all zeroes, assuming that this is a new wiki or any
// repopulations where done manually via script.
$row = self::doLoadFromDB( $lb->getConnection( DB_MASTER ) );
}
- if ( !self::isSane( $row ) ) {
+ if ( !self::isRowSane( $row ) ) {
wfDebug( __METHOD__ . ": site_stats persistently nonsensical o_O\n" );
// Always return a row-like object
- $row = (object)array_fill_keys( self::selectFields(), 0 );
+ $row = self::salvageInsaneRow( $row );
}
return $row;
}
- /**
- * @param IDatabase $db
- * @return stdClass|bool
- */
- private static function doLoadFromDB( IDatabase $db ) {
- return $db->selectRow(
- 'site_stats',
- self::selectFields(),
- [ 'ss_row_id' => 1 ],
- __METHOD__
- );
- }
-
/**
* @return int
*/
public static function edits() {
self::load();
- return self::$row->ss_total_edits;
+ return (int)self::$row->ss_total_edits;
}
/**
public static function articles() {
self::load();
- return self::$row->ss_good_articles;
+ return (int)self::$row->ss_good_articles;
}
/**
public static function pages() {
self::load();
- return self::$row->ss_total_pages;
+ return (int)self::$row->ss_total_pages;
}
/**
public static function users() {
self::load();
- return self::$row->ss_users;
+ return (int)self::$row->ss_users;
}
/**
public static function activeUsers() {
self::load();
- return self::$row->ss_active_users;
+ return (int)self::$row->ss_active_users;
}
/**
public static function images() {
self::load();
- return self::$row->ss_images;
+ return (int)self::$row->ss_images;
}
/**
];
}
+ /**
+ * @param IDatabase $db
+ * @return stdClass|bool
+ */
+ private static function doLoadFromDB( IDatabase $db ) {
+ return $db->selectRow(
+ 'site_stats',
+ self::selectFields(),
+ [ 'ss_row_id' => 1 ],
+ __METHOD__
+ );
+ }
+
/**
* Is the provided row of site stats sane, or should it be regenerated?
*
* @param bool|object $row
* @return bool
*/
- private static function isSane( $row ) {
+ private static function isRowSane( $row ) {
if ( $row === false
|| $row->ss_total_pages < $row->ss_good_articles
|| $row->ss_total_edits < $row->ss_total_pages
'ss_users',
'ss_images',
] as $member ) {
- if ( $row->$member > 2000000000 || $row->$member < 0 ) {
+ if ( $row->$member < 0 ) {
return false;
}
}
return true;
}
+ /**
+ * @param stdClass|bool $row
+ * @return stdClass
+ */
+ private static function salvageInsaneRow( $row ) {
+ $map = $row ? (array)$row : [];
+ // Fill in any missing values with zero
+ $map += array_fill_keys( self::selectFields(), 0 );
+ // Convert negative values to zero
+ foreach ( $map as $field => $value ) {
+ $map[$field] = max( 0, $value );
+ }
+
+ return (object)$row;
+ }
+
/**
* @return LoadBalancer
*/
/**
* @param int $index
+ * @param string[] $groups
* @return IDatabase
*/
- private static function getDB( $index ) {
- return MediaWikiServices::getInstance()->getDBLoadBalancer()->getConnection( $index );
+ private static function getDB( $index, $groups = [] ) {
+ $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
+
+ return $lb->getConnection( $index, $groups );
}
}
if ( $title ) {
$this->logger->info(
__METHOD__ . ' fell back to READ_LATEST and got a Title.',
- [ 'trace' => wfDebugBacktrace() ]
+ [ 'trace' => wfBacktrace() ]
);
return $title;
}
'showlinkedto' => false,
];
- if ( $config->get( 'AllowCategorizedRecentChanges' ) ) {
- $ret += [
- 'categories' => [
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_ISMULTI => true,
- ],
- 'categories_any' => false,
- ];
- }
-
return $ret;
}
$popts->enableLimitReport( !$params['disablepp'] && !$params['disablelimitreport'] );
$popts->setIsPreview( $params['preview'] || $params['sectionpreview'] );
$popts->setIsSectionPreview( $params['sectionpreview'] );
- $popts->setEditSection( !$params['disableeditsection'] );
if ( $params['disabletidy'] ) {
$popts->setTidy( false );
}
"apihelp-feedrecentchanges-param-tagfilter": "Filter by tag.",
"apihelp-feedrecentchanges-param-target": "Show only changes on pages linked from this page.",
"apihelp-feedrecentchanges-param-showlinkedto": "Show changes on pages linked to the selected page instead.",
- "apihelp-feedrecentchanges-param-categories": "Show only changes on pages in all of these categories.",
- "apihelp-feedrecentchanges-param-categories_any": "Show only changes on pages in any of the categories instead.",
"apihelp-feedrecentchanges-example-simple": "Show recent changes.",
"apihelp-feedrecentchanges-example-30days": "Show recent changes for 30 days.",
"apihelp-parse-param-disablepp": "Em vez deste, usar <var>$1disablelimitreport</var>.",
"apihelp-parse-param-disableeditsection": "Omitir as hiperligações para edição da secção no resultado da análise sintática.",
"apihelp-parse-param-disabletidy": "Não fazer a limpeza do HTML (isto é, o ''tidy'') no resultado da análise sintática.",
+ "apihelp-parse-param-disablestylededuplication": "Não desduplica as folhas de estilo incluídas na saída do analisador sintático.",
"apihelp-parse-param-generatexml": "Gerar a árvore de análise XML (requer o modelo de conteúdo <code>$1</code>; substituído por <kbd>$2prop=parsetree</kbd>).",
"apihelp-parse-param-preview": "Executar a análise em modo de antevisão.",
"apihelp-parse-param-sectionpreview": "Executar a análise em modo de antevisão (também ativa o modo de antevisão).",
"apihelp-feedrecentchanges-param-tagfilter": "{{doc-apihelp-param|feedrecentchanges|tagfilter}}",
"apihelp-feedrecentchanges-param-target": "{{doc-apihelp-param|feedrecentchanges|target}}",
"apihelp-feedrecentchanges-param-showlinkedto": "{{doc-apihelp-param|feedrecentchanges|showlinkedto}}",
- "apihelp-feedrecentchanges-param-categories": "{{doc-apihelp-param|feedrecentchanges|categories}}",
- "apihelp-feedrecentchanges-param-categories_any": "{{doc-apihelp-param|feedrecentchanges|categories_any}}",
"apihelp-feedrecentchanges-example-simple": "{{doc-apihelp-example|feedrecentchanges}}",
"apihelp-feedrecentchanges-example-30days": "{{doc-apihelp-example|feedrecentchanges}}",
"apihelp-feedwatchlist-summary": "{{doc-apihelp-summary|feedwatchlist}}",
// ParserOptions for it. And don't cache this ParserOptions
// either.
$po = ParserOptions::newFromAnon();
- $po->setEditSection( false );
$po->setAllowUnsafeRawHtml( false );
return $po;
}
$this->mParserOptions = new ParserOptions;
- $this->mParserOptions->setEditSection( false );
// Messages may take parameters that could come
// from malicious sources. As a precaution, disable
// the <html> parser tag when parsing messages.
$first--;
}
# Get net change
- $charDifference = $this->formatCharacterDifference( $block[$first], $block[$last] );
+ $charDifference = $this->formatCharacterDifference( $block[$first], $block[$last] ) ?: false;
}
$numberofWatchingusers = $this->numberofWatchingusers( $block[0]->numberofWatchingusers );
}
function __destruct() {
- if ( $this->mOpened ) {
+ if ( $this->opened ) {
Wikimedia\suppressWarnings();
$this->close();
Wikimedia\restoreWarnings();
}
$this->close();
- $this->mUser = $user;
- $this->mPassword = $password;
+ $this->user = $user;
+ $this->password = $password;
// changed internal variables functions
// mServer now holds the TNS endpoint
// mDBname is schema name if different from username
if ( !$server ) {
// backward compatibillity (server used to be null and TNS was supplied in dbname)
- $this->mServer = $dbName;
- $this->mDBname = $user;
+ $this->server = $dbName;
+ $this->dbName = $user;
} else {
- $this->mServer = $server;
+ $this->server = $server;
if ( !$dbName ) {
- $this->mDBname = $user;
+ $this->dbName = $user;
} else {
- $this->mDBname = $dbName;
+ $this->dbName = $dbName;
}
}
$this->setFlag( DBO_PERSISTENT );
}
- $session_mode = $this->mFlags & DBO_SYSDBA ? OCI_SYSDBA : OCI_DEFAULT;
+ $session_mode = $this->flags & DBO_SYSDBA ? OCI_SYSDBA : OCI_DEFAULT;
Wikimedia\suppressWarnings();
- if ( $this->mFlags & DBO_PERSISTENT ) {
- $this->mConn = oci_pconnect(
- $this->mUser,
- $this->mPassword,
- $this->mServer,
+ if ( $this->flags & DBO_PERSISTENT ) {
+ $this->conn = oci_pconnect(
+ $this->user,
+ $this->password,
+ $this->server,
$this->defaultCharset,
$session_mode
);
- } elseif ( $this->mFlags & DBO_DEFAULT ) {
- $this->mConn = oci_new_connect(
- $this->mUser,
- $this->mPassword,
- $this->mServer,
+ } elseif ( $this->flags & DBO_DEFAULT ) {
+ $this->conn = oci_new_connect(
+ $this->user,
+ $this->password,
+ $this->server,
$this->defaultCharset,
$session_mode
);
} else {
- $this->mConn = oci_connect(
- $this->mUser,
- $this->mPassword,
- $this->mServer,
+ $this->conn = oci_connect(
+ $this->user,
+ $this->password,
+ $this->server,
$this->defaultCharset,
$session_mode
);
}
Wikimedia\restoreWarnings();
- if ( $this->mUser != $this->mDBname ) {
+ if ( $this->user != $this->dbName ) {
// change current schema in session
- $this->selectDB( $this->mDBname );
+ $this->selectDB( $this->dbName );
}
- if ( !$this->mConn ) {
+ if ( !$this->conn ) {
throw new DBConnectionError( $this, $this->lastError() );
}
- $this->mOpened = true;
+ $this->opened = true;
# removed putenv calls because they interfere with the system globaly
$this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
$this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
$this->doQuery( 'ALTER SESSION SET NLS_NUMERIC_CHARACTERS=\'.,\'' );
- return $this->mConn;
+ return $this->conn;
}
/**
* @return bool
*/
protected function closeConnection() {
- return oci_close( $this->mConn );
+ return oci_close( $this->conn );
}
function execFlags() {
- return $this->mTrxLevel ? OCI_NO_AUTO_COMMIT : OCI_COMMIT_ON_SUCCESS;
+ return $this->trxLevel ? OCI_NO_AUTO_COMMIT : OCI_COMMIT_ON_SUCCESS;
}
protected function doQuery( $sql ) {
Wikimedia\suppressWarnings();
- $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql );
+ $this->mLastResult = $stmt = oci_parse( $this->conn, $sql );
if ( $stmt === false ) {
- $e = oci_error( $this->mConn );
+ $e = oci_error( $this->conn );
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
return false;
}
function lastError() {
- if ( $this->mConn === false ) {
+ if ( $this->conn === false ) {
$e = oci_error();
} else {
- $e = oci_error( $this->mConn );
+ $e = oci_error( $this->conn );
}
return $e['message'];
}
function lastErrno() {
- if ( $this->mConn === false ) {
+ if ( $this->conn === false ) {
$e = oci_error();
} else {
- $e = oci_error( $this->mConn );
+ $e = oci_error( $this->conn );
}
return $e['code'];
}
$sql .= ')';
- $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql );
+ $this->mLastResult = $stmt = oci_parse( $this->conn, $sql );
if ( $stmt === false ) {
- $e = oci_error( $this->mConn );
+ $e = oci_error( $this->conn );
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
return false;
}
} else {
/** @var OCI_Lob[] $lob */
- $lob[$col] = oci_new_descriptor( $this->mConn, OCI_D_LOB );
+ $lob[$col] = oci_new_descriptor( $this->conn, OCI_D_LOB );
if ( $lob[$col] === false ) {
$e = oci_error( $stmt );
throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
}
}
- if ( !$this->mTrxLevel ) {
- oci_commit( $this->mConn );
+ if ( !$this->trxLevel ) {
+ oci_commit( $this->conn );
}
return oci_free_statement( $stmt );
FROM all_sequences asq, all_tab_columns atc
WHERE decode(
atc.table_name,
- '{$this->mTablePrefix}MWUSER',
- '{$this->mTablePrefix}USER',
+ '{$this->tablePrefix}MWUSER',
+ '{$this->tablePrefix}USER',
atc.table_name
) || '_' ||
- atc.column_name || '_SEQ' = '{$this->mTablePrefix}' || asq.sequence_name
- AND asq.sequence_owner = upper('{$this->mDBname}')
- AND atc.owner = upper('{$this->mDBname}')" );
+ atc.column_name || '_SEQ' = '{$this->tablePrefix}' || asq.sequence_name
+ AND asq.sequence_owner = upper('{$this->dbName}')
+ AND atc.owner = upper('{$this->dbName}')" );
while ( ( $row = $result->fetchRow() ) !== false ) {
$this->sequenceData[$row[1]] = [
$newName = strtoupper( $newName );
$oldName = strtoupper( $oldName );
- $tabName = substr( $newName, strlen( $this->mTablePrefix ) );
+ $tabName = substr( $newName, strlen( $this->tablePrefix ) );
$oldPrefix = substr( $oldName, 0, strlen( $oldName ) - strlen( $tabName ) );
- $newPrefix = strtoupper( $this->mTablePrefix );
+ $newPrefix = strtoupper( $this->tablePrefix );
return $this->doQuery( "BEGIN DUPLICATE_TABLE( '$tabName', " .
"'$oldPrefix', '$newPrefix', $temporary ); END;" );
$listWhere = ' AND table_name LIKE \'' . strtoupper( $prefix ) . '%\'';
}
- $owner = strtoupper( $this->mDBname );
+ $owner = strtoupper( $this->dbName );
$result = $this->doQuery( "SELECT table_name FROM all_tables " .
"WHERE owner='$owner' AND table_name NOT LIKE '%!_IDX\$_' ESCAPE '!' $listWhere" );
);
$row = $rset->fetchRow();
if ( !$row ) {
- return oci_server_version( $this->mConn );
+ return oci_server_version( $this->conn );
}
return $row['version'];
$table = $this->tableName( $table );
$table = strtoupper( $this->removeIdentifierQuotes( $table ) );
$index = strtoupper( $index );
- $owner = strtoupper( $this->mDBname );
+ $owner = strtoupper( $this->dbName );
$sql = "SELECT 1 FROM all_indexes WHERE owner='$owner' AND index_name='{$table}_{$index}'";
$res = $this->doQuery( $sql );
if ( $res ) {
function tableExists( $table, $fname = __METHOD__ ) {
$table = $this->tableName( $table );
$table = $this->addQuotes( strtoupper( $this->removeIdentifierQuotes( $table ) ) );
- $owner = $this->addQuotes( strtoupper( $this->mDBname ) );
+ $owner = $this->addQuotes( strtoupper( $this->dbName ) );
$sql = "SELECT 1 FROM all_tables WHERE owner=$owner AND table_name=$table";
$res = $this->doQuery( $sql );
if ( $res && $res->numRows() > 0 ) {
}
$fieldInfoStmt = oci_parse(
- $this->mConn,
+ $this->conn,
'SELECT * FROM wiki_field_info_full WHERE table_name ' .
$tableWhere . ' and column_name = \'' . $field . '\''
);
}
protected function doBegin( $fname = __METHOD__ ) {
- $this->mTrxLevel = 1;
+ $this->trxLevel = 1;
$this->doQuery( 'SET CONSTRAINTS ALL DEFERRED' );
}
protected function doCommit( $fname = __METHOD__ ) {
- if ( $this->mTrxLevel ) {
- $ret = oci_commit( $this->mConn );
+ if ( $this->trxLevel ) {
+ $ret = oci_commit( $this->conn );
if ( !$ret ) {
throw new DBUnexpectedError( $this, $this->lastError() );
}
- $this->mTrxLevel = 0;
+ $this->trxLevel = 0;
$this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
}
}
protected function doRollback( $fname = __METHOD__ ) {
- if ( $this->mTrxLevel ) {
- oci_rollback( $this->mConn );
- $this->mTrxLevel = 0;
+ if ( $this->trxLevel ) {
+ oci_rollback( $this->conn );
+ $this->trxLevel = 0;
$this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
}
}
}
function selectDB( $db ) {
- $this->mDBname = $db;
- if ( $db == null || $db == $this->mUser ) {
+ $this->dbName = $db;
+ if ( $db == null || $db == $this->user ) {
return true;
}
$sql = 'ALTER SESSION SET CURRENT_SCHEMA=' . strtoupper( $db );
- $stmt = oci_parse( $this->mConn, $sql );
+ $stmt = oci_parse( $this->conn, $sql );
Wikimedia\suppressWarnings();
$success = oci_execute( $stmt );
Wikimedia\restoreWarnings();
$sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
}
- $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql );
+ $this->mLastResult = $stmt = oci_parse( $this->conn, $sql );
if ( $stmt === false ) {
- $e = oci_error( $this->mConn );
+ $e = oci_error( $this->conn );
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
return false;
}
} else {
/** @var OCI_Lob[] $lob */
- $lob[$col] = oci_new_descriptor( $this->mConn, OCI_D_LOB );
+ $lob[$col] = oci_new_descriptor( $this->conn, OCI_D_LOB );
if ( $lob[$col] === false ) {
$e = oci_error( $stmt );
throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
}
}
- if ( !$this->mTrxLevel ) {
- oci_commit( $this->mConn );
+ if ( !$this->trxLevel ) {
+ oci_commit( $this->conn );
}
return oci_free_statement( $stmt );
}
function getDBname() {
- return $this->mDBname;
+ return $this->dbName;
}
function getServer() {
- return $this->mServer;
+ return $this->server;
}
public function buildGroupConcatField(
$pd = [];
if ( $config->get( 'SiteStatsAsyncFactor' ) ) {
// Lock the table so we don't have double DB/memcached updates
- if ( !$dbw->lockIsFree( $lockKey, __METHOD__ )
- || !$dbw->lock( $lockKey, __METHOD__, 1 ) // 1 sec timeout
- ) {
+ if ( !$dbw->lock( $lockKey, __METHOD__, 0 ) ) {
$this->doUpdatePendingDeltas();
return;
*/
protected function getParserOutput( WikiPage $page, Revision $rev ) {
$parserOptions = $page->makeParserOptions( $this->getContext() );
-
- if ( !$rev->isCurrent() || !$rev->getTitle()->quickUserCan( 'edit', $this->getUser() ) ) {
- $parserOptions->setEditSection( false );
- }
-
$parserOutput = $page->getParserOutput( $parserOptions, $rev->getId() );
return $parserOutput;
} elseif ( self::isCommandLine() ) {
$message = $this->getText();
// T17602: STDERR may not be available
- if ( defined( 'STDERR' ) ) {
+ if ( !defined( 'MW_PHPUNIT_TEST' ) && defined( 'STDERR' ) ) {
fwrite( STDERR, $message );
} else {
echo $message;
$this->parserTitle = Title::newFromText( 'Installer' );
$this->parserOptions = new ParserOptions( $wgUser ); // language will be wrong :(
- $this->parserOptions->setEditSection( false );
// Don't try to access DB before user language is initialised
$this->setParserLanguage( Language::factory( 'en' ) );
}
"config-apc": "[http://www.php.net/apc APC] ta instaláu",
"config-apcu": "[http://www.php.net/apcu APCu] ta instaláu",
"config-wincache": "[https://www.iis.net/download/WinCacheForPhp WinCache] ta instaláu",
- "config-no-cache-apcu": "<strong>Warning:</strong> Non pudo atopase[http://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].\nEl caxé d'oxetos nun ta activáu.",
+ "config-no-cache-apcu": "<strong>Atención:</strong> Nun pudo alcontrase [http://www.php.net/apcu APCu] o [http://www.iis.net/download/WinCacheForPhp WinCache].\nLa caché d'oxetos nun ta activada.",
"config-mod-security": "<strong>Alvertencia:</strong> El to servidor web tien activáu [https://modsecurity.org/mod_security]/mod_security2 .Munches de les sos configuraciones comunes pueden causar problemes a MediaWiki o otru software que dexe a los usuarios publicar conteníu arbitrario. De ser posible, tendríes de desactivalo. Si non, consulta la [https://modsecurity.org/documentation/ mod_security documentation] o contacta col alministrador del to servidor si atopes erros aleatorios.",
"config-diff3-bad": "Nun s'alcontró GNU diff3.",
"config-git": "Alcontróse'l software de control de versiones Git: <code>$1</code>.",
"@metadata": {
"authors": [
"Dferg",
- "Seb35"
+ "Seb35",
+ "MarcoAurelio"
]
},
- "mainpagedocfooter": "Consulte usted la [https://meta.wikimedia.org/wiki/Help:Contents/es Guía de usuario] para obtener información sobre el uso del software wiki.\n\n== Empezando ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista de ajustes de configuración]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ/es FAQ de MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correo de anuncios de distribución de MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Regionalizar MediaWiki para su idioma]"
+ "config-localsettings-upgrade": "Se ha encontrado un archivo <code>LocalSettings.php</code>.\nPara actualizar esta instalación, escriba el valor de <code>$wgUpgradeKey</code> en el cuadro de abajo.\nLo encontrará en <code>LocalSettings.php</code>.",
+ "config-localsettings-cli-upgrade": "Se ha detectado un archivo <code>LocalSettings.php</code>.\nPara actualizar la instalación, en su lugar ejecute <code>update.php</code>",
+ "config-localsettings-connection-error": "Se ha producido un error al conectar con la base de datos a través de la configuración especificada en <code>LocalSettings.php</code>. Corrija estos ajustes e inténtelo de nuevo.\n\n$1",
+ "config-your-language-help": "Seleccione un idioma para usar durante el proceso de instalación.",
+ "config-page-welcome": "Le damos la bienvenida a MediaWiki.",
+ "config-page-readme": "Léame",
+ "config-help-restart": "¿Desea borrar todos los datos guardados que ha escrito y reiniciar el proceso de instalación?",
+ "config-env-good": "El entorno ha sido comprobado.\nPuede instalar MediaWiki.",
+ "config-env-bad": "El entorno ha sido comprobado.\nNo puede instalar MediaWiki.",
+ "mainpagedocfooter": "Consulte la [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents guía] para obtener información sobre el uso del software wiki.\n\n== Primeros pasos ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Lista de ajustes de configuración]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Preguntas frecuentes sobre MediaWiki]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correo de anuncios de publicación de MediaWiki]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Traducir MediaWiki a su idioma]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Aprenda a combatir el spam en su wiki]"
}
"authors": [
"McDutchie",
"아라",
- "Macofe"
+ "Macofe",
+ "Fanjiayi"
]
},
"config-desc": "Le installator de MediaWiki",
"config-apc": "[http://www.php.net/apc APC] es installate",
"config-apcu": "[http://www.php.net/apcu APCu] es installate",
"config-wincache": "[https://www.iis.net/download/WinCacheForPhp WinCache] es installate",
- "config-no-cache-apcu": "<strong>Attention:</strong> Impossibile trovar [http://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].\nLe cache de objectos non es activate.",
+ "config-no-cache-apcu": "<strong>Attention:</strong> Impossibile trovar [http://www.php.net/apcu APCu] o [http://www.iis.net/download/WinCacheForPhp WinCache].\nLe cache de objectos non es activate.",
"config-mod-security": "<strong>Attention</strong>: [https://modsecurity.org/ mod_security]/mod_security2 es active in tu servitor web. Multe configurationes commun de isto causa problemas pro MediaWiki o altere software que permitte al usatores de publicar contento arbitrari. Si possibile, isto deberea esser disactivate.\nAlteremente, consulta le [https://modsecurity.org/documentation/ documentation de mod_security] o contacta le servicio de adjuta de tu servitor si tu incontra estranie errores.",
"config-diff3-bad": "GNU diff3 non trovate.",
"config-git": "Systema de controlo de version Git trovate: <code>$1</code>",
"config-cache-options": "Configuration del cache de objectos:",
"config-cache-help": "Le cache de objectos es usate pro meliorar le rapiditate de MediaWiki per immagazinar le datos frequentemente usate.\nLe sitos medie o grande es multo incoragiate de activar isto, ma anque le sitos parve percipera le beneficios.",
"config-cache-none": "Nulle cache (nulle functionalitate es removite, ma le rapiditate pote diminuer in grande sitos wiki)",
- "config-cache-accel": "Cache de objectos PHP (APC, APCu, XCache o WinCache)",
+ "config-cache-accel": "Cache de objectos PHP (APC, APCu o WinCache)",
"config-cache-memcached": "Usar Memcached (require additional installation e configuration)",
"config-memcached-servers": "Servitores Memcached:",
"config-memcached-help": "Lista de adresses IP a usar pro Memcached.\nDebe specificar un per linea e specificar le porto a usar. Per exemplo:\n 127.0.0.1:11211\n 192.168.1.25:1234",
"config-apc": "[http://www.php.net/apc APC] är installerat",
"config-apcu": "[http://www.php.net/apcu APCu] är installerat",
"config-wincache": "[https://www.iis.net/download/WinCacheForPhp WinCache] är installerat",
- "config-no-cache-apcu": "'''Varning:''' Kunde inte hitta [http://www.php.net/apcu APCu], [http://xcache.lighttpd.net/ XCache] eller [http://www.iis.net/download/WinCacheForPhp WinCache].\nCachelagring av objekt är inte aktiverat.",
+ "config-no-cache-apcu": "<strong>Varning:</strong> Kunde inte hitta [http://www.php.net/apcu APCu] eller [http://www.iis.net/download/WinCacheForPhp WinCache].\nCachelagring av objekt är inte aktiverat.",
"config-mod-security": "'''Varning:''' Din webbserver har [https://modsecurity.org/ mod_security] aktiverat. Om felaktigt konfigurerat kan den skapa problem för MediaWiki eller annan programvara som tillåter användaren att posta godtyckligt innehåll.\nTitta på [https://modsecurity.org/documentation/ mod_security-dokumentationen] eller kontakta din värd om du påträffar slumpmässiga fel.",
"config-diff3-bad": "GNU diff3 hittades inte.",
"config-git": "Hittade Git-mjukvara för versionskontroll: <code>$1</code>.",
"config-cache-options": "Inställningar för cachelagring av objekt:",
"config-cache-help": "Cachelagring av objekt används för att förbättra hastigheten på MediaWiki genom att cachelagra data som används ofta.\nMedelstora till stora webbplatser är starkt uppmuntrade att aktivera detta, och små webbplatser kommer även att se fördelar.",
"config-cache-none": "Ingen cachelagring (ingen funktionalitet tas bort, men hastighet kan påverkas på större wiki-webbplatser)",
- "config-cache-accel": "Cachelagring av PHP-objekt (APC, APCu, XCache eller WinCache)",
+ "config-cache-accel": "Cachelagring av PHP-objekt (APC, APCu eller WinCache)",
"config-cache-memcached": "Använda Memcached (kräver ytterligare inställningar och konfiguration)",
"config-memcached-servers": "Memcached-servrar:",
"config-memcached-help": "Lista över IP-adresser som ska användas för Memcached.\nBör ange en per rad och specificera den port som ska användas. Till exempel:\n 127.0.0.1:11211\n 192.168.1.25:1234",
$lockKey = wfWikiID() . ':recentchanges-prune';
$dbw = wfGetDB( DB_MASTER );
- if ( !$dbw->lockIsFree( $lockKey, __METHOD__ )
- || !$dbw->lock( $lockKey, __METHOD__, 1 )
- ) {
- return; // already in progress
+ if ( !$dbw->lock( $lockKey, __METHOD__, 0 ) ) {
+ // already in progress
+ return;
}
$factory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
$dbw->setSessionOptions( [ 'connTimeout' => 900 ] );
$lockKey = wfWikiID() . '-activeusers';
- if ( !$dbw->lockIsFree( $lockKey, __METHOD__ ) || !$dbw->lock( $lockKey, __METHOD__, 1 ) ) {
+ if ( !$dbw->lock( $lockKey, __METHOD__, 0 ) ) {
// Exclusive update (avoids duplicate entries)… it's usually fine to just drop out here,
// if the Job is already running.
return;
--- /dev/null
+<?php
+/**
+ * Job that purges expired user group memberships.
+ *
+ * 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 3 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 JobQueue
+ */
+
+class UserGroupExpiryJob extends Job {
+ public function __construct( $params = false ) {
+ parent::__construct( 'userGroupExpiry', Title::newMainPage(), $params );
+ $this->removeDuplicates = true;
+ }
+
+ /**
+ * Run the job
+ * @return bool Success
+ */
+ public function run() {
+ UserGroupMembership::purgeExpired();
+
+ return true;
+ }
+}
}
/**
- * Locally set a key to expire soon if it is stale based on $purgeTimestamp
+ * Set a key to soon expire in the local cluster if it pre-dates $purgeTimestamp
*
* This sets stale keys' time-to-live at HOLDOFF_TTL seconds, which both avoids
* broadcasting in mcrouter setups and also avoids races with new tombstones.
}
/**
- * Locally set a "check" key to expire soon if it is stale based on $purgeTimestamp
+ * Set a "check" key to soon expire in the local cluster if it pre-dates $purgeTimestamp
*
* @param string $key Cache key
* @param int $purgeTimestamp UNIX timestamp of purge
const SMALL_WRITE_ROWS = 100;
/** @var string SQL query */
- protected $mLastQuery = '';
+ protected $lastQuery = '';
/** @var float|bool UNIX timestamp of last write query */
- protected $mLastWriteTime = false;
+ protected $lastWriteTime = false;
/** @var string|bool */
- protected $mPHPError = false;
+ protected $phpError = false;
/** @var string */
- protected $mServer;
+ protected $server;
/** @var string */
- protected $mUser;
+ protected $user;
/** @var string */
- protected $mPassword;
+ protected $password;
/** @var string */
- protected $mDBname;
+ protected $dbName;
/** @var array[] $aliases Map of (table => (dbname, schema, prefix) map) */
protected $tableAliases = [];
/** @var bool Whether this PHP instance is for a CLI script */
protected $errorLogger;
/** @var resource|null Database connection */
- protected $mConn = null;
+ protected $conn = null;
/** @var bool */
- protected $mOpened = false;
+ protected $opened = false;
/** @var array[] List of (callable, method name) */
- protected $mTrxIdleCallbacks = [];
+ protected $trxIdleCallbacks = [];
/** @var array[] List of (callable, method name) */
- protected $mTrxPreCommitCallbacks = [];
+ protected $trxPreCommitCallbacks = [];
/** @var array[] List of (callable, method name) */
- protected $mTrxEndCallbacks = [];
+ protected $trxEndCallbacks = [];
/** @var callable[] Map of (name => callable) */
- protected $mTrxRecurringCallbacks = [];
+ protected $trxRecurringCallbacks = [];
/** @var bool Whether to suppress triggering of transaction end callbacks */
- protected $mTrxEndCallbacksSuppressed = false;
+ protected $trxEndCallbacksSuppressed = false;
/** @var string */
- protected $mTablePrefix = '';
+ protected $tablePrefix = '';
/** @var string */
- protected $mSchema = '';
+ protected $schema = '';
/** @var int */
- protected $mFlags;
+ protected $flags;
/** @var array */
- protected $mLBInfo = [];
- /** @var bool|null */
- protected $mDefaultBigSelects = null;
+ protected $lbInfo = [];
/** @var array|bool */
- protected $mSchemaVars = false;
+ protected $schemaVars = false;
/** @var array */
- protected $mSessionVars = [];
+ protected $sessionVars = [];
/** @var array|null */
protected $preparedArgs;
/** @var string|bool|null Stashed value of html_errors INI setting */
*
* @var int
*/
- protected $mTrxLevel = 0;
+ protected $trxLevel = 0;
/**
* Either a short hexidecimal string if a transaction is active or ""
*
* @var string
- * @see Database::mTrxLevel
+ * @see Database::trxLevel
*/
- protected $mTrxShortId = '';
+ protected $trxShortId = '';
/**
* The UNIX time that the transaction started. Callers can assume that if
* snapshot isolation is used, then the data is *at least* up to date to that
* point (possibly more up-to-date since the first SELECT defines the snapshot).
*
* @var float|null
- * @see Database::mTrxLevel
+ * @see Database::trxLevel
*/
- private $mTrxTimestamp = null;
+ private $trxTimestamp = null;
/** @var float Lag estimate at the time of BEGIN */
- private $mTrxReplicaLag = null;
+ private $trxReplicaLag = null;
/**
* Remembers the function name given for starting the most recent transaction via begin().
* Used to provide additional context for error reporting.
*
* @var string
- * @see Database::mTrxLevel
+ * @see Database::trxLevel
*/
- private $mTrxFname = null;
+ private $trxFname = null;
/**
* Record if possible write queries were done in the last transaction started
*
* @var bool
- * @see Database::mTrxLevel
+ * @see Database::trxLevel
*/
- private $mTrxDoneWrites = false;
+ private $trxDoneWrites = false;
/**
* Record if the current transaction was started implicitly due to DBO_TRX being set.
*
* @var bool
- * @see Database::mTrxLevel
+ * @see Database::trxLevel
*/
- private $mTrxAutomatic = false;
+ private $trxAutomatic = false;
/**
* Array of levels of atomicity within transactions
*
* @var array
*/
- private $mTrxAtomicLevels = [];
+ private $trxAtomicLevels = [];
/**
* Record if the current transaction was started implicitly by Database::startAtomic
*
* @var bool
*/
- private $mTrxAutomaticAtomic = false;
+ private $trxAutomaticAtomic = false;
/**
* Track the write query callers of the current transaction
*
* @var string[]
*/
- private $mTrxWriteCallers = [];
+ private $trxWriteCallers = [];
/**
* @var float Seconds spent in write queries for the current transaction
*/
- private $mTrxWriteDuration = 0.0;
+ private $trxWriteDuration = 0.0;
/**
* @var int Number of write queries for the current transaction
*/
- private $mTrxWriteQueryCount = 0;
+ private $trxWriteQueryCount = 0;
/**
* @var int Number of rows affected by write queries for the current transaction
*/
- private $mTrxWriteAffectedRows = 0;
+ private $trxWriteAffectedRows = 0;
/**
- * @var float Like mTrxWriteQueryCount but excludes lock-bound, easy to replicate, queries
+ * @var float Like trxWriteQueryCount but excludes lock-bound, easy to replicate, queries
*/
- private $mTrxWriteAdjDuration = 0.0;
+ private $trxWriteAdjDuration = 0.0;
/**
- * @var int Number of write queries counted in mTrxWriteAdjDuration
+ * @var int Number of write queries counted in trxWriteAdjDuration
*/
- private $mTrxWriteAdjQueryCount = 0;
+ private $trxWriteAdjQueryCount = 0;
/**
* @var float RTT time estimate
*/
- private $mRTTEstimate = 0.0;
+ private $rttEstimate = 0.0;
/** @var array Map of (name => 1) for locks obtained via lock() */
- private $mNamedLocksHeld = [];
+ private $namedLocksHeld = [];
/** @var array Map of (table name => 1) for TEMPORARY tables */
- protected $mSessionTempTables = [];
+ protected $sessionTempTables = [];
/** @var IDatabase|null Lazy handle to the master DB this server replicates from */
private $lazyMasterHandle;
/** @var float UNIX timestamp */
protected $lastPing = 0.0;
- /** @var int[] Prior mFlags values */
+ /** @var int[] Prior flags member variable values */
private $priorFlags = [];
/** @var object|string Class name or object With profileIn/profileOut methods */
$password = $params['password'];
$dbName = $params['dbname'];
- $this->mSchema = $params['schema'];
- $this->mTablePrefix = $params['tablePrefix'];
+ $this->schema = $params['schema'];
+ $this->tablePrefix = $params['tablePrefix'];
$this->cliMode = $params['cliMode'];
// Agent name is added to SQL queries in a comment, so make sure it can't break out
$this->agent = str_replace( '/', '-', $params['agent'] );
- $this->mFlags = $params['flags'];
- if ( $this->mFlags & self::DBO_DEFAULT ) {
+ $this->flags = $params['flags'];
+ if ( $this->flags & self::DBO_DEFAULT ) {
if ( $this->cliMode ) {
- $this->mFlags &= ~self::DBO_TRX;
+ $this->flags &= ~self::DBO_TRX;
} else {
- $this->mFlags |= self::DBO_TRX;
+ $this->flags |= self::DBO_TRX;
}
}
- $this->mSessionVars = $params['variables'];
+ $this->sessionVars = $params['variables'];
$this->srvCache = isset( $params['srvCache'] )
? $params['srvCache']
}
// Set the domain object after open() sets the relevant fields
- if ( $this->mDBname != '' ) {
+ if ( $this->dbName != '' ) {
// Domains with server scope but a table prefix are not used by IDatabase classes
- $this->currentDomain = new DatabaseDomain( $this->mDBname, null, $this->mTablePrefix );
+ $this->currentDomain = new DatabaseDomain( $this->dbName, null, $this->tablePrefix );
}
}
if ( $ignoreErrors !== null ) {
// setFlag()/clearFlag() do not allow DBO_IGNORE changes for sanity
if ( $ignoreErrors ) {
- $this->mFlags |= self::DBO_IGNORE;
+ $this->flags |= self::DBO_IGNORE;
} else {
- $this->mFlags &= ~self::DBO_IGNORE;
+ $this->flags &= ~self::DBO_IGNORE;
}
}
}
public function trxLevel() {
- return $this->mTrxLevel;
+ return $this->trxLevel;
}
public function trxTimestamp() {
- return $this->mTrxLevel ? $this->mTrxTimestamp : null;
+ return $this->trxLevel ? $this->trxTimestamp : null;
}
public function tablePrefix( $prefix = null ) {
- $old = $this->mTablePrefix;
+ $old = $this->tablePrefix;
if ( $prefix !== null ) {
- $this->mTablePrefix = $prefix;
- $this->currentDomain = ( $this->mDBname != '' )
- ? new DatabaseDomain( $this->mDBname, null, $this->mTablePrefix )
+ $this->tablePrefix = $prefix;
+ $this->currentDomain = ( $this->dbName != '' )
+ ? new DatabaseDomain( $this->dbName, null, $this->tablePrefix )
: DatabaseDomain::newUnspecified();
}
}
public function dbSchema( $schema = null ) {
- $old = $this->mSchema;
+ $old = $this->schema;
if ( $schema !== null ) {
- $this->mSchema = $schema;
+ $this->schema = $schema;
}
return $old;
public function getLBInfo( $name = null ) {
if ( is_null( $name ) ) {
- return $this->mLBInfo;
+ return $this->lbInfo;
} else {
- if ( array_key_exists( $name, $this->mLBInfo ) ) {
- return $this->mLBInfo[$name];
+ if ( array_key_exists( $name, $this->lbInfo ) ) {
+ return $this->lbInfo[$name];
} else {
return null;
}
public function setLBInfo( $name, $value = null ) {
if ( is_null( $value ) ) {
- $this->mLBInfo = $name;
+ $this->lbInfo = $name;
} else {
- $this->mLBInfo[$name] = $value;
+ $this->lbInfo[$name] = $value;
}
}
}
public function lastQuery() {
- return $this->mLastQuery;
+ return $this->lastQuery;
}
public function doneWrites() {
- return (bool)$this->mLastWriteTime;
+ return (bool)$this->lastWriteTime;
}
public function lastDoneWrites() {
- return $this->mLastWriteTime ?: false;
+ return $this->lastWriteTime ?: false;
}
public function writesPending() {
- return $this->mTrxLevel && $this->mTrxDoneWrites;
+ return $this->trxLevel && $this->trxDoneWrites;
}
public function writesOrCallbacksPending() {
- return $this->mTrxLevel && (
- $this->mTrxDoneWrites || $this->mTrxIdleCallbacks || $this->mTrxPreCommitCallbacks
+ return $this->trxLevel && (
+ $this->trxDoneWrites || $this->trxIdleCallbacks || $this->trxPreCommitCallbacks
);
}
public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
- if ( !$this->mTrxLevel ) {
+ if ( !$this->trxLevel ) {
return false;
- } elseif ( !$this->mTrxDoneWrites ) {
+ } elseif ( !$this->trxDoneWrites ) {
return 0.0;
}
switch ( $type ) {
case self::ESTIMATE_DB_APPLY:
$this->ping( $rtt );
- $rttAdjTotal = $this->mTrxWriteAdjQueryCount * $rtt;
- $applyTime = max( $this->mTrxWriteAdjDuration - $rttAdjTotal, 0 );
+ $rttAdjTotal = $this->trxWriteAdjQueryCount * $rtt;
+ $applyTime = max( $this->trxWriteAdjDuration - $rttAdjTotal, 0 );
// For omitted queries, make them count as something at least
- $omitted = $this->mTrxWriteQueryCount - $this->mTrxWriteAdjQueryCount;
+ $omitted = $this->trxWriteQueryCount - $this->trxWriteAdjQueryCount;
$applyTime += self::TINY_WRITE_SEC * $omitted;
return $applyTime;
default: // everything
- return $this->mTrxWriteDuration;
+ return $this->trxWriteDuration;
}
}
public function pendingWriteCallers() {
- return $this->mTrxLevel ? $this->mTrxWriteCallers : [];
+ return $this->trxLevel ? $this->trxWriteCallers : [];
}
public function pendingWriteRowsAffected() {
- return $this->mTrxWriteAffectedRows;
+ return $this->trxWriteAffectedRows;
}
/**
* @return array
*/
protected function pendingWriteAndCallbackCallers() {
- if ( !$this->mTrxLevel ) {
+ if ( !$this->trxLevel ) {
return [];
}
- $fnames = $this->mTrxWriteCallers;
+ $fnames = $this->trxWriteCallers;
foreach ( [
- $this->mTrxIdleCallbacks,
- $this->mTrxPreCommitCallbacks,
- $this->mTrxEndCallbacks
+ $this->trxIdleCallbacks,
+ $this->trxPreCommitCallbacks,
+ $this->trxEndCallbacks
] as $callbacks ) {
foreach ( $callbacks as $callback ) {
$fnames[] = $callback[1];
}
public function isOpen() {
- return $this->mOpened;
+ return $this->opened;
}
public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
}
if ( $remember === self::REMEMBER_PRIOR ) {
- array_push( $this->priorFlags, $this->mFlags );
+ array_push( $this->priorFlags, $this->flags );
}
- $this->mFlags |= $flag;
+ $this->flags |= $flag;
}
public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
}
if ( $remember === self::REMEMBER_PRIOR ) {
- array_push( $this->priorFlags, $this->mFlags );
+ array_push( $this->priorFlags, $this->flags );
}
- $this->mFlags &= ~$flag;
+ $this->flags &= ~$flag;
}
public function restoreFlags( $state = self::RESTORE_PRIOR ) {
}
if ( $state === self::RESTORE_INITIAL ) {
- $this->mFlags = reset( $this->priorFlags );
+ $this->flags = reset( $this->priorFlags );
$this->priorFlags = [];
} else {
- $this->mFlags = array_pop( $this->priorFlags );
+ $this->flags = array_pop( $this->priorFlags );
}
}
public function getFlag( $flag ) {
- return !!( $this->mFlags & $flag );
+ return !!( $this->flags & $flag );
}
/**
* Set a custom error handler for logging errors during database connection
*/
protected function installErrorHandler() {
- $this->mPHPError = false;
+ $this->phpError = false;
$this->htmlErrors = ini_set( 'html_errors', '0' );
set_error_handler( [ $this, 'connectionErrorLogger' ] );
}
* @return string|bool Last PHP error for this DB (typically connection errors)
*/
protected function getLastPHPError() {
- if ( $this->mPHPError ) {
- $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError );
+ if ( $this->phpError ) {
+ $error = preg_replace( '!\[<a.*</a>\]!', '', $this->phpError );
$error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
return $error;
* @param string $errstr
*/
public function connectionErrorLogger( $errno, $errstr ) {
- $this->mPHPError = $errstr;
+ $this->phpError = $errstr;
}
/**
protected function getLogContext( array $extras = [] ) {
return array_merge(
[
- 'db_server' => $this->mServer,
- 'db_name' => $this->mDBname,
- 'db_user' => $this->mUser,
+ 'db_server' => $this->server,
+ 'db_name' => $this->dbName,
+ 'db_user' => $this->user,
],
$extras
);
}
public function close() {
- if ( $this->mConn ) {
+ if ( $this->conn ) {
if ( $this->trxLevel() ) {
$this->commit( __METHOD__, self::FLUSHING_INTERNAL );
}
$closed = $this->closeConnection();
- $this->mConn = false;
+ $this->conn = false;
} elseif (
- $this->mTrxIdleCallbacks ||
- $this->mTrxPreCommitCallbacks ||
- $this->mTrxEndCallbacks
+ $this->trxIdleCallbacks ||
+ $this->trxPreCommitCallbacks ||
+ $this->trxEndCallbacks
) { // sanity
throw new RuntimeException( "Transaction callbacks still pending." );
} else {
$closed = true;
}
- $this->mOpened = false;
+ $this->opened = false;
return $closed;
}
$sql,
$matches
) ) {
- $this->mSessionTempTables[$matches[1]] = 1;
+ $this->sessionTempTables[$matches[1]] = 1;
return true;
} elseif ( preg_match(
$sql,
$matches
) ) {
- $isTemp = isset( $this->mSessionTempTables[$matches[1]] );
- unset( $this->mSessionTempTables[$matches[1]] );
+ $isTemp = isset( $this->sessionTempTables[$matches[1]] );
+ unset( $this->sessionTempTables[$matches[1]] );
return $isTemp;
} elseif ( preg_match(
$sql,
$matches
) ) {
- return isset( $this->mSessionTempTables[$matches[1]] );
+ return isset( $this->sessionTempTables[$matches[1]] );
} elseif ( preg_match(
'/^(?:INSERT\s+(?:\w+\s+)?INTO|UPDATE|DELETE\s+FROM)\s+[`"\']?(\w+)[`"\']?/i',
$sql,
$matches
) ) {
- return isset( $this->mSessionTempTables[$matches[1]] );
+ return isset( $this->sessionTempTables[$matches[1]] );
}
return false;
public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
$priorWritesPending = $this->writesOrCallbacksPending();
- $this->mLastQuery = $sql;
+ $this->lastQuery = $sql;
$isWrite = $this->isWriteQuery( $sql );
if ( $isWrite ) {
throw new DBReadOnlyError( $this, "Database is read-only: $reason" );
}
# Set a flag indicating that writes have been done
- $this->mLastWriteTime = microtime( true );
+ $this->lastWriteTime = microtime( true );
}
# Add trace comment to the begin of the sql string, right after the operator.
$commentedSql = preg_replace( '/\s|$/', " /* $fname {$this->agent} */ ", $sql, 1 );
# Start implicit transactions that wrap the request if DBO_TRX is enabled
- if ( !$this->mTrxLevel && $this->getFlag( self::DBO_TRX )
+ if ( !$this->trxLevel && $this->getFlag( self::DBO_TRX )
&& $this->isTransactableQuery( $sql )
) {
$this->begin( __METHOD__ . " ($fname)", self::TRANSACTION_INTERNAL );
- $this->mTrxAutomatic = true;
+ $this->trxAutomatic = true;
}
# Keep track of whether the transaction has write queries pending
- if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $isWrite ) {
- $this->mTrxDoneWrites = true;
+ if ( $this->trxLevel && !$this->trxDoneWrites && $isWrite ) {
+ $this->trxDoneWrites = true;
$this->trxProfiler->transactionWritingIn(
- $this->mServer, $this->mDBname, $this->mTrxShortId );
+ $this->server, $this->dbName, $this->trxShortId );
}
if ( $this->getFlag( self::DBO_DEBUG ) ) {
- $this->queryLogger->debug( "{$this->mDBname} {$commentedSql}" );
+ $this->queryLogger->debug( "{$this->dbName} {$commentedSql}" );
}
# Avoid fatals if close() was called
}
# Include query transaction state
- $queryProf .= $this->mTrxShortId ? " [TRX#{$this->mTrxShortId}]" : "";
+ $queryProf .= $this->trxShortId ? " [TRX#{$this->trxShortId}]" : "";
$startTime = microtime( true );
if ( $this->profiler ) {
if ( $ret !== false ) {
$this->lastPing = $startTime;
- if ( $isWrite && $this->mTrxLevel ) {
+ if ( $isWrite && $this->trxLevel ) {
$this->updateTrxWriteQueryTime( $sql, $queryRuntime, $this->affectedRows() );
- $this->mTrxWriteCallers[] = $fname;
+ $this->trxWriteCallers[] = $fname;
}
}
if ( $sql === self::PING_QUERY ) {
- $this->mRTTEstimate = $queryRuntime;
+ $this->rttEstimate = $queryRuntime;
}
$this->trxProfiler->recordQueryCompletion(
}
}
- $this->mTrxWriteDuration += $runtime;
- $this->mTrxWriteQueryCount += 1;
- $this->mTrxWriteAffectedRows += $affected;
+ $this->trxWriteDuration += $runtime;
+ $this->trxWriteQueryCount += 1;
+ $this->trxWriteAffectedRows += $affected;
if ( $indicativeOfReplicaRuntime ) {
- $this->mTrxWriteAdjDuration += $runtime;
- $this->mTrxWriteAdjQueryCount += 1;
+ $this->trxWriteAdjDuration += $runtime;
+ $this->trxWriteAdjQueryCount += 1;
}
}
# Dropped connections also mean that named locks are automatically released.
# Only allow error suppression in autocommit mode or when the lost transaction
# didn't matter anyway (aside from DBO_TRX snapshot loss).
- if ( $this->mNamedLocksHeld ) {
+ if ( $this->namedLocksHeld ) {
return false; // possible critical section violation
} elseif ( $sql === 'COMMIT' ) {
return !$priorWritesPending; // nothing written anyway? (T127428)
* @return null|Exception
*/
private function handleSessionLoss() {
- $this->mTrxLevel = 0;
- $this->mTrxIdleCallbacks = []; // T67263
- $this->mTrxPreCommitCallbacks = []; // T67263
- $this->mSessionTempTables = [];
- $this->mNamedLocksHeld = [];
+ $this->trxLevel = 0;
+ $this->trxIdleCallbacks = []; // T67263
+ $this->trxPreCommitCallbacks = []; // T67263
+ $this->sessionTempTables = [];
+ $this->namedLocksHeld = [];
try {
- // Handle callbacks in mTrxEndCallbacks
+ // Handle callbacks in trxEndCallbacks
$this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
$this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
return null;
$this->tableNamesWithIndexClauseOrJOIN(
$table, $useIndexes, $ignoreIndexes, $join_conds );
} elseif ( $table != '' ) {
- if ( $table[0] == ' ' ) {
- $from = ' FROM ' . $table;
- } else {
- $from = ' FROM ' .
- $this->tableNamesWithIndexClauseOrJOIN(
- [ $table ], $useIndexes, $ignoreIndexes, [] );
- }
+ $from = ' FROM ' .
+ $this->tableNamesWithIndexClauseOrJOIN(
+ [ $table ], $useIndexes, $ignoreIndexes, [] );
} else {
$from = '';
}
public function tableExists( $table, $fname = __METHOD__ ) {
$tableRaw = $this->tableName( $table, 'raw' );
- if ( isset( $this->mSessionTempTables[$tableRaw] ) ) {
+ if ( isset( $this->sessionTempTables[$tableRaw] ) ) {
return true; // already known to exist
}
# Stub. Shouldn't cause serious problems if it's not overridden, but
# if your database engine supports a concept similar to MySQL's
# databases you may as well.
- $this->mDBname = $db;
+ $this->dbName = $db;
return true;
}
public function getDBname() {
- return $this->mDBname;
+ return $this->dbName;
}
public function getServer() {
- return $this->mServer;
+ return $this->server;
}
public function tableName( $name, $format = 'quoted' ) {
$database = $this->tableAliases[$table]['dbname'];
$schema = is_string( $this->tableAliases[$table]['schema'] )
? $this->tableAliases[$table]['schema']
- : $this->mSchema;
+ : $this->schema;
$prefix = is_string( $this->tableAliases[$table]['prefix'] )
? $this->tableAliases[$table]['prefix']
- : $this->mTablePrefix;
+ : $this->tablePrefix;
} else {
$database = '';
- $schema = $this->mSchema; # Default schema
- $prefix = $this->mTablePrefix; # Default prefix
+ $schema = $this->schema; # Default schema
+ $prefix = $this->tablePrefix; # Default prefix
}
}
}
$affectedRowCount = 0;
- $useTrx = !$this->mTrxLevel;
+ $useTrx = !$this->trxLevel;
if ( $useTrx ) {
$this->begin( $fname, self::TRANSACTION_INTERNAL );
}
}
final public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
- if ( !$this->mTrxLevel ) {
+ if ( !$this->trxLevel ) {
throw new DBUnexpectedError( $this, "No transaction is active." );
}
- $this->mTrxEndCallbacks[] = [ $callback, $fname ];
+ $this->trxEndCallbacks[] = [ $callback, $fname ];
}
final public function onTransactionIdle( callable $callback, $fname = __METHOD__ ) {
- $this->mTrxIdleCallbacks[] = [ $callback, $fname ];
- if ( !$this->mTrxLevel ) {
+ $this->trxIdleCallbacks[] = [ $callback, $fname ];
+ if ( !$this->trxLevel ) {
$this->runOnTransactionIdleCallbacks( self::TRIGGER_IDLE );
}
}
final public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
- if ( $this->mTrxLevel || $this->getFlag( self::DBO_TRX ) ) {
+ if ( $this->trxLevel || $this->getFlag( self::DBO_TRX ) ) {
// As long as DBO_TRX is set, writes will accumulate until the load balancer issues
// an implicit commit of all peer databases. This is true even if a transaction has
// not yet been triggered by writes; make sure $callback runs *after* any such writes.
- $this->mTrxPreCommitCallbacks[] = [ $callback, $fname ];
+ $this->trxPreCommitCallbacks[] = [ $callback, $fname ];
} else {
// No transaction is active nor will start implicitly, so make one for this callback
$this->startAtomic( __METHOD__ );
final public function setTransactionListener( $name, callable $callback = null ) {
if ( $callback ) {
- $this->mTrxRecurringCallbacks[$name] = $callback;
+ $this->trxRecurringCallbacks[$name] = $callback;
} else {
- unset( $this->mTrxRecurringCallbacks[$name] );
+ unset( $this->trxRecurringCallbacks[$name] );
}
}
* @since 1.28
*/
final public function setTrxEndCallbackSuppression( $suppress ) {
- $this->mTrxEndCallbacksSuppressed = $suppress;
+ $this->trxEndCallbacksSuppressed = $suppress;
}
/**
* @throws Exception
*/
public function runOnTransactionIdleCallbacks( $trigger ) {
- if ( $this->mTrxEndCallbacksSuppressed ) {
+ if ( $this->trxEndCallbacksSuppressed ) {
return;
}
$e = null; // first exception
do { // callbacks may add callbacks :)
$callbacks = array_merge(
- $this->mTrxIdleCallbacks,
- $this->mTrxEndCallbacks // include "transaction resolution" callbacks
+ $this->trxIdleCallbacks,
+ $this->trxEndCallbacks // include "transaction resolution" callbacks
);
- $this->mTrxIdleCallbacks = []; // consumed (and recursion guard)
- $this->mTrxEndCallbacks = []; // consumed (recursion guard)
+ $this->trxIdleCallbacks = []; // consumed (and recursion guard)
+ $this->trxEndCallbacks = []; // consumed (recursion guard)
foreach ( $callbacks as $callback ) {
try {
list( $phpCallback ) = $callback;
}
}
}
- } while ( count( $this->mTrxIdleCallbacks ) );
+ } while ( count( $this->trxIdleCallbacks ) );
if ( $e instanceof Exception ) {
throw $e; // re-throw any first exception
public function runOnTransactionPreCommitCallbacks() {
$e = null; // first exception
do { // callbacks may add callbacks :)
- $callbacks = $this->mTrxPreCommitCallbacks;
- $this->mTrxPreCommitCallbacks = []; // consumed (and recursion guard)
+ $callbacks = $this->trxPreCommitCallbacks;
+ $this->trxPreCommitCallbacks = []; // consumed (and recursion guard)
foreach ( $callbacks as $callback ) {
try {
list( $phpCallback ) = $callback;
$e = $e ?: $ex;
}
}
- } while ( count( $this->mTrxPreCommitCallbacks ) );
+ } while ( count( $this->trxPreCommitCallbacks ) );
if ( $e instanceof Exception ) {
throw $e; // re-throw any first exception
* @since 1.20
*/
public function runTransactionListenerCallbacks( $trigger ) {
- if ( $this->mTrxEndCallbacksSuppressed ) {
+ if ( $this->trxEndCallbacksSuppressed ) {
return;
}
/** @var Exception $e */
$e = null; // first exception
- foreach ( $this->mTrxRecurringCallbacks as $phpCallback ) {
+ foreach ( $this->trxRecurringCallbacks as $phpCallback ) {
try {
$phpCallback( $trigger, $this );
} catch ( Exception $ex ) {
}
final public function startAtomic( $fname = __METHOD__ ) {
- if ( !$this->mTrxLevel ) {
+ if ( !$this->trxLevel ) {
$this->begin( $fname, self::TRANSACTION_INTERNAL );
// If DBO_TRX is set, a series of startAtomic/endAtomic pairs will result
// in all changes being in one transaction to keep requests transactional.
if ( !$this->getFlag( self::DBO_TRX ) ) {
- $this->mTrxAutomaticAtomic = true;
+ $this->trxAutomaticAtomic = true;
}
}
- $this->mTrxAtomicLevels[] = $fname;
+ $this->trxAtomicLevels[] = $fname;
}
final public function endAtomic( $fname = __METHOD__ ) {
- if ( !$this->mTrxLevel ) {
+ if ( !$this->trxLevel ) {
throw new DBUnexpectedError( $this, "No atomic transaction is open (got $fname)." );
}
- if ( !$this->mTrxAtomicLevels ||
- array_pop( $this->mTrxAtomicLevels ) !== $fname
+ if ( !$this->trxAtomicLevels ||
+ array_pop( $this->trxAtomicLevels ) !== $fname
) {
throw new DBUnexpectedError( $this, "Invalid atomic section ended (got $fname)." );
}
- if ( !$this->mTrxAtomicLevels && $this->mTrxAutomaticAtomic ) {
+ if ( !$this->trxAtomicLevels && $this->trxAutomaticAtomic ) {
$this->commit( $fname, self::FLUSHING_INTERNAL );
}
}
final public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT ) {
// Protect against mismatched atomic section, transaction nesting, and snapshot loss
- if ( $this->mTrxLevel ) {
- if ( $this->mTrxAtomicLevels ) {
- $levels = implode( ', ', $this->mTrxAtomicLevels );
+ if ( $this->trxLevel ) {
+ if ( $this->trxAtomicLevels ) {
+ $levels = implode( ', ', $this->trxAtomicLevels );
$msg = "$fname: Got explicit BEGIN while atomic section(s) $levels are open.";
throw new DBUnexpectedError( $this, $msg );
- } elseif ( !$this->mTrxAutomatic ) {
- $msg = "$fname: Explicit transaction already active (from {$this->mTrxFname}).";
+ } elseif ( !$this->trxAutomatic ) {
+ $msg = "$fname: Explicit transaction already active (from {$this->trxFname}).";
throw new DBUnexpectedError( $this, $msg );
} else {
// @TODO: make this an exception at some point
- $msg = "$fname: Implicit transaction already active (from {$this->mTrxFname}).";
+ $msg = "$fname: Implicit transaction already active (from {$this->trxFname}).";
$this->queryLogger->error( $msg );
return; // join the main transaction set
}
$this->assertOpen();
$this->doBegin( $fname );
- $this->mTrxTimestamp = microtime( true );
- $this->mTrxFname = $fname;
- $this->mTrxDoneWrites = false;
- $this->mTrxAutomaticAtomic = false;
- $this->mTrxAtomicLevels = [];
- $this->mTrxShortId = sprintf( '%06x', mt_rand( 0, 0xffffff ) );
- $this->mTrxWriteDuration = 0.0;
- $this->mTrxWriteQueryCount = 0;
- $this->mTrxWriteAffectedRows = 0;
- $this->mTrxWriteAdjDuration = 0.0;
- $this->mTrxWriteAdjQueryCount = 0;
- $this->mTrxWriteCallers = [];
+ $this->trxTimestamp = microtime( true );
+ $this->trxFname = $fname;
+ $this->trxDoneWrites = false;
+ $this->trxAutomaticAtomic = false;
+ $this->trxAtomicLevels = [];
+ $this->trxShortId = sprintf( '%06x', mt_rand( 0, 0xffffff ) );
+ $this->trxWriteDuration = 0.0;
+ $this->trxWriteQueryCount = 0;
+ $this->trxWriteAffectedRows = 0;
+ $this->trxWriteAdjDuration = 0.0;
+ $this->trxWriteAdjQueryCount = 0;
+ $this->trxWriteCallers = [];
// First SELECT after BEGIN will establish the snapshot in REPEATABLE-READ.
// Get an estimate of the replica DB lag before then, treating estimate staleness
// as lag itself just to be safe
$status = $this->getApproximateLagStatus();
- $this->mTrxReplicaLag = $status['lag'] + ( microtime( true ) - $status['since'] );
+ $this->trxReplicaLag = $status['lag'] + ( microtime( true ) - $status['since'] );
// T147697: make explicitTrxActive() return true until begin() finishes. This way, no
// caller will think its OK to muck around with the transaction just because startAtomic()
- // has not yet completed (e.g. setting mTrxAtomicLevels).
- $this->mTrxAutomatic = ( $mode === self::TRANSACTION_INTERNAL );
+ // has not yet completed (e.g. setting trxAtomicLevels).
+ $this->trxAutomatic = ( $mode === self::TRANSACTION_INTERNAL );
}
/**
*/
protected function doBegin( $fname ) {
$this->query( 'BEGIN', $fname );
- $this->mTrxLevel = 1;
+ $this->trxLevel = 1;
}
final public function commit( $fname = __METHOD__, $flush = '' ) {
- if ( $this->mTrxLevel && $this->mTrxAtomicLevels ) {
+ if ( $this->trxLevel && $this->trxAtomicLevels ) {
// There are still atomic sections open. This cannot be ignored
- $levels = implode( ', ', $this->mTrxAtomicLevels );
+ $levels = implode( ', ', $this->trxAtomicLevels );
throw new DBUnexpectedError(
$this,
"$fname: Got COMMIT while atomic sections $levels are still open."
}
if ( $flush === self::FLUSHING_INTERNAL || $flush === self::FLUSHING_ALL_PEERS ) {
- if ( !$this->mTrxLevel ) {
+ if ( !$this->trxLevel ) {
return; // nothing to do
- } elseif ( !$this->mTrxAutomatic ) {
+ } elseif ( !$this->trxAutomatic ) {
throw new DBUnexpectedError(
$this,
"$fname: Flushing an explicit transaction, getting out of sync."
);
}
} else {
- if ( !$this->mTrxLevel ) {
+ if ( !$this->trxLevel ) {
$this->queryLogger->error(
"$fname: No transaction to commit, something got out of sync." );
return; // nothing to do
- } elseif ( $this->mTrxAutomatic ) {
+ } elseif ( $this->trxAutomatic ) {
// @TODO: make this an exception at some point
$msg = "$fname: Explicit commit of implicit transaction.";
$this->queryLogger->error( $msg );
$this->runOnTransactionPreCommitCallbacks();
$writeTime = $this->pendingWriteQueryDuration( self::ESTIMATE_DB_APPLY );
$this->doCommit( $fname );
- if ( $this->mTrxDoneWrites ) {
- $this->mLastWriteTime = microtime( true );
+ if ( $this->trxDoneWrites ) {
+ $this->lastWriteTime = microtime( true );
$this->trxProfiler->transactionWritingOut(
- $this->mServer,
- $this->mDBname,
- $this->mTrxShortId,
+ $this->server,
+ $this->dbName,
+ $this->trxShortId,
$writeTime,
- $this->mTrxWriteAffectedRows
+ $this->trxWriteAffectedRows
);
}
* @param string $fname
*/
protected function doCommit( $fname ) {
- if ( $this->mTrxLevel ) {
+ if ( $this->trxLevel ) {
$this->query( 'COMMIT', $fname );
- $this->mTrxLevel = 0;
+ $this->trxLevel = 0;
}
}
final public function rollback( $fname = __METHOD__, $flush = '' ) {
if ( $flush === self::FLUSHING_INTERNAL || $flush === self::FLUSHING_ALL_PEERS ) {
- if ( !$this->mTrxLevel ) {
+ if ( !$this->trxLevel ) {
return; // nothing to do
}
} else {
- if ( !$this->mTrxLevel ) {
+ if ( !$this->trxLevel ) {
$this->queryLogger->error(
"$fname: No transaction to rollback, something got out of sync." );
return; // nothing to do
$this->assertOpen();
$this->doRollback( $fname );
- $this->mTrxAtomicLevels = [];
- if ( $this->mTrxDoneWrites ) {
+ $this->trxAtomicLevels = [];
+ if ( $this->trxDoneWrites ) {
$this->trxProfiler->transactionWritingOut(
- $this->mServer,
- $this->mDBname,
- $this->mTrxShortId
+ $this->server,
+ $this->dbName,
+ $this->trxShortId
);
}
- $this->mTrxIdleCallbacks = []; // clear
- $this->mTrxPreCommitCallbacks = []; // clear
+ $this->trxIdleCallbacks = []; // clear
+ $this->trxPreCommitCallbacks = []; // clear
try {
$this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
} catch ( Exception $e ) {
* @param string $fname
*/
protected function doRollback( $fname ) {
- if ( $this->mTrxLevel ) {
+ if ( $this->trxLevel ) {
# Disconnects cause rollback anyway, so ignore those errors
$ignoreErrors = true;
$this->query( 'ROLLBACK', $fname, $ignoreErrors );
- $this->mTrxLevel = 0;
+ $this->trxLevel = 0;
}
}
}
public function explicitTrxActive() {
- return $this->mTrxLevel && ( $this->mTrxAtomicLevels || !$this->mTrxAutomatic );
+ return $this->trxLevel && ( $this->trxAtomicLevels || !$this->trxAutomatic );
}
public function duplicateTableStructure(
public function ping( &$rtt = null ) {
// Avoid hitting the server if it was hit recently
if ( $this->isOpen() && ( microtime( true ) - $this->lastPing ) < self::PING_TTL ) {
- if ( !func_num_args() || $this->mRTTEstimate > 0 ) {
- $rtt = $this->mRTTEstimate;
+ if ( !func_num_args() || $this->rttEstimate > 0 ) {
+ $rtt = $this->rttEstimate;
return true; // don't care about $rtt
}
}
$this->restoreFlags( self::RESTORE_PRIOR );
if ( $ok ) {
- $rtt = $this->mRTTEstimate;
+ $rtt = $this->rttEstimate;
}
return $ok;
*/
protected function reconnect() {
$this->closeConnection();
- $this->mOpened = false;
- $this->mConn = false;
+ $this->opened = false;
+ $this->conn = false;
try {
- $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
+ $this->open( $this->server, $this->user, $this->password, $this->dbName );
$this->lastPing = microtime( true );
$ok = true;
} catch ( DBConnectionError $e ) {
* @since 1.27
*/
protected function getTransactionLagStatus() {
- return $this->mTrxLevel
- ? [ 'lag' => $this->mTrxReplicaLag, 'since' => $this->trxTimestamp() ]
+ return $this->trxLevel
+ ? [ 'lag' => $this->trxReplicaLag, 'since' => $this->trxTimestamp() ]
: null;
}
}
public function setSchemaVars( $vars ) {
- $this->mSchemaVars = $vars;
+ $this->schemaVars = $vars;
}
public function sourceStream(
* @return array
*/
protected function getSchemaVars() {
- if ( $this->mSchemaVars ) {
- return $this->mSchemaVars;
+ if ( $this->schemaVars ) {
+ return $this->schemaVars;
} else {
return $this->getDefaultSchemaVars();
}
}
public function lockIsFree( $lockName, $method ) {
- return true;
+ // RDBMs methods for checking named locks may or may not count this thread itself.
+ // In MySQL, IS_FREE_LOCK() returns 0 if the thread already has the lock. This is
+ // the behavior choosen by the interface for this method.
+ return !isset( $this->namedLocksHeld[$lockName] );
}
public function lock( $lockName, $method, $timeout = 5 ) {
- $this->mNamedLocksHeld[$lockName] = 1;
+ $this->namedLocksHeld[$lockName] = 1;
return true;
}
public function unlock( $lockName, $method ) {
- unset( $this->mNamedLocksHeld[$lockName] );
+ unset( $this->namedLocksHeld[$lockName] );
return true;
}
}
/**
- * Get the underlying binding handle, mConn
+ * Get the underlying binding connection handle
*
- * Makes sure that mConn is set (disconnects and ping() failure can unset it).
+ * Makes sure the connection resource is set (disconnects and ping() failure can unset it).
* This catches broken callers than catch and ignore disconnection exceptions.
* Unlike checking isOpen(), this is safe to call inside of open().
*
* @since 1.26
*/
protected function getBindingHandle() {
- if ( !$this->mConn ) {
+ if ( !$this->conn ) {
throw new DBUnexpectedError(
$this,
'DB connection was already closed or the connection dropped.'
);
}
- return $this->mConn;
+ return $this->conn;
}
/**
* @return string
*/
public function __toString() {
- return (string)$this->mConn;
+ return (string)$this->conn;
}
/**
if ( $this->isOpen() ) {
// Open a new connection resource without messing with the old one
- $this->mOpened = false;
- $this->mConn = false;
- $this->mTrxEndCallbacks = []; // don't copy
+ $this->opened = false;
+ $this->conn = false;
+ $this->trxEndCallbacks = []; // don't copy
$this->handleSessionLoss(); // no trx or locks anymore
- $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
+ $this->open( $this->server, $this->user, $this->password, $this->dbName );
$this->lastPing = microtime( true );
}
}
* Run a few simple sanity checks and close dangling connections
*/
public function __destruct() {
- if ( $this->mTrxLevel && $this->mTrxDoneWrites ) {
- trigger_error( "Uncommitted DB writes (transaction from {$this->mTrxFname})." );
+ if ( $this->trxLevel && $this->trxDoneWrites ) {
+ trigger_error( "Uncommitted DB writes (transaction from {$this->trxFname})." );
}
$danglingWriters = $this->pendingWriteAndCallbackCallers();
trigger_error( "DB transaction writes or callbacks still pending ($fnames)." );
}
- if ( $this->mConn ) {
+ if ( $this->conn ) {
// Avoid connection leaks for sanity. Normally, resources close at script completion.
// The connection might already be closed in zend/hhvm by now, so suppress warnings.
Wikimedia\suppressWarnings();
$this->closeConnection();
Wikimedia\restoreWarnings();
- $this->mConn = false;
- $this->mOpened = false;
+ $this->conn = false;
+ $this->opened = false;
}
}
}
}
$this->close();
- $this->mServer = $server;
- $this->mUser = $user;
- $this->mPassword = $password;
- $this->mDBname = $dbName;
+ $this->server = $server;
+ $this->user = $user;
+ $this->password = $password;
+ $this->dbName = $dbName;
$connectionInfo = [];
}
Wikimedia\suppressWarnings();
- $this->mConn = sqlsrv_connect( $server, $connectionInfo );
+ $this->conn = sqlsrv_connect( $server, $connectionInfo );
Wikimedia\restoreWarnings();
- if ( $this->mConn === false ) {
+ if ( $this->conn === false ) {
throw new DBConnectionError( $this, $this->lastError() );
}
- $this->mOpened = true;
+ $this->opened = true;
- return $this->mConn;
+ return $this->conn;
}
/**
* @return bool
*/
protected function closeConnection() {
- return sqlsrv_close( $this->mConn );
+ return sqlsrv_close( $this->conn );
}
/**
if ( $this->mPrepareStatements ) {
// we do prepare + execute so we can get its field metadata for later usage if desired
- $stmt = sqlsrv_prepare( $this->mConn, $sql, [], $scrollArr );
+ $stmt = sqlsrv_prepare( $this->conn, $sql, [], $scrollArr );
$success = sqlsrv_execute( $stmt );
} else {
- $stmt = sqlsrv_query( $this->mConn, $sql, [], $scrollArr );
+ $stmt = sqlsrv_query( $this->conn, $sql, [], $scrollArr );
$success = (bool)$stmt;
}
* @return string Version information from the database
*/
public function getServerVersion() {
- $server_info = sqlsrv_server_info( $this->mConn );
+ $server_info = sqlsrv_server_info( $this->conn );
$version = 'Error';
if ( isset( $server_info['SQLServerVersion'] ) ) {
$version = $server_info['SQLServerVersion'];
}
if ( $schema === false ) {
- $schema = $this->mSchema;
+ $schema = $this->schema;
}
$res = $this->query( "SELECT 1 FROM INFORMATION_SCHEMA.TABLES
* @param string $fname
*/
protected function doBegin( $fname = __METHOD__ ) {
- sqlsrv_begin_transaction( $this->mConn );
- $this->mTrxLevel = 1;
+ sqlsrv_begin_transaction( $this->conn );
+ $this->trxLevel = 1;
}
/**
* @param string $fname
*/
protected function doCommit( $fname = __METHOD__ ) {
- sqlsrv_commit( $this->mConn );
- $this->mTrxLevel = 0;
+ sqlsrv_commit( $this->conn );
+ $this->trxLevel = 0;
}
/**
* @param string $fname
*/
protected function doRollback( $fname = __METHOD__ ) {
- sqlsrv_rollback( $this->mConn );
- $this->mTrxLevel = 0;
+ sqlsrv_rollback( $this->conn );
+ $this->trxLevel = 0;
}
/**
*/
public function selectDB( $db ) {
try {
- $this->mDBname = $db;
+ $this->dbName = $db;
$this->query( "USE $db" );
return true;
} catch ( Exception $e ) {
private function populateColumnCaches() {
$res = $this->select( 'INFORMATION_SCHEMA.COLUMNS', '*',
[
- 'TABLE_CATALOG' => $this->mDBname,
- 'TABLE_SCHEMA' => $this->mSchema,
+ 'TABLE_CATALOG' => $this->dbName,
+ 'TABLE_SCHEMA' => $this->schema,
'DATA_TYPE' => [ 'varbinary', 'binary', 'image', 'bit' ]
] );
protected $sqlMode;
/** @var bool Use experimental UTF-8 transmission encoding */
protected $utf8Mode;
+ /** @var bool|null */
+ protected $defaultBigSelects = null;
/** @var string|null */
private $serverVersion = null;
# Close/unset connection handle
$this->close();
- $this->mServer = $server;
- $this->mUser = $user;
- $this->mPassword = $password;
- $this->mDBname = $dbName;
+ $this->server = $server;
+ $this->user = $user;
+ $this->password = $password;
+ $this->dbName = $dbName;
$this->installErrorHandler();
try {
- $this->mConn = $this->mysqlConnect( $this->mServer );
+ $this->conn = $this->mysqlConnect( $this->server );
} catch ( Exception $ex ) {
$this->restoreErrorHandler();
throw $ex;
$error = $this->restoreErrorHandler();
# Always log connection errors
- if ( !$this->mConn ) {
+ if ( !$this->conn ) {
if ( !$error ) {
$error = $this->lastError();
}
] )
);
$this->queryLogger->debug(
- "Error selecting database $dbName on server {$this->mServer}" );
+ "Error selecting database $dbName on server {$this->server}" );
$this->reportConnectionError( "Error selecting database $dbName" );
}
}
// Set any custom settings defined by site config
// (e.g. https://dev.mysql.com/doc/refman/4.1/en/innodb-parameters.html)
- foreach ( $this->mSessionVars as $var => $val ) {
+ foreach ( $this->sessionVars as $var => $val ) {
// Escape strings but not numbers to avoid MySQL complaining
if ( !is_int( $val ) && !is_float( $val ) ) {
$val = $this->addQuotes( $val );
}
}
- $this->mOpened = true;
+ $this->opened = true;
return true;
}
* @return string
*/
public function lastError() {
- if ( $this->mConn ) {
+ if ( $this->conn ) {
# Even if it's non-zero, it can still be invalid
Wikimedia\suppressWarnings();
- $error = $this->mysqlError( $this->mConn );
+ $error = $this->mysqlError( $this->conn );
if ( !$error ) {
$error = $this->mysqlError();
}
$error = $this->mysqlError();
}
if ( $error ) {
- $error .= ' (' . $this->mServer . ')';
+ $error .= ' (' . $this->server . ')';
}
return $error;
list( $database, , $prefix, $table ) = $this->qualifiedTableComponents( $table );
$tableName = "{$prefix}{$table}";
- if ( isset( $this->mSessionTempTables[$tableName] ) ) {
+ if ( isset( $this->sessionTempTables[$tableName] ) ) {
return true; // already known to exist and won't show in SHOW TABLES anyway
}
return 0; // already reached this point for sure
}
- $useGTID = ( $this->useGTIDs && $pos->gtids );
-
// Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
- if ( $useGTID ) {
+ if ( $pos->gtids ) {
// Wait on the GTID set (MariaDB only)
$gtidArg = $this->addQuotes( implode( ',', $pos->gtids ) );
$res = $this->doQuery( "SELECT MASTER_GTID_WAIT($gtidArg, $timeout)" );
} else {
// Wait on the binlog coordinates
- $encFile = $this->addQuotes( $pos->file );
- $encPos = intval( $pos->pos );
+ $encFile = $this->addQuotes( $pos->getLogFile() );
+ $encPos = intval( $pos->pos[1] );
$res = $this->doQuery( "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)" );
}
// Result can be NULL (error), -1 (timeout), or 0+ per the MySQL manual
$status = ( $row[0] !== null ) ? intval( $row[0] ) : null;
if ( $status === null ) {
- if ( !$useGTID ) {
+ if ( !$pos->gtids ) {
// T126436: jobs programmed to wait on master positions might be referencing
// binlogs with an old master hostname; this makes MASTER_POS_WAIT() return null.
// Try to detect this case and treat the replica DB as having reached the given
* @return MySQLMasterPos|bool
*/
public function getReplicaPos() {
- $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
- $row = $this->fetchObject( $res );
+ $now = microtime( true );
- if ( $row ) {
- $pos = $row->Exec_Master_Log_Pos;
- // Also fetch the last-applied GTID set (MariaDB)
- if ( $this->useGTIDs ) {
- $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_slave_pos'", __METHOD__ );
- $gtidRow = $this->fetchObject( $res );
- $gtidSet = $gtidRow ? $gtidRow->Value : '';
- } else {
- $gtidSet = '';
+ if ( $this->useGTIDs ) {
+ $res = $this->query( "SELECT @@global.gtid_slave_pos AS Value", __METHOD__ );
+ $gtidRow = $this->fetchObject( $res );
+ if ( $gtidRow && strlen( $gtidRow->Value ) ) {
+ return new MySQLMasterPos( $gtidRow->Value, $now );
}
+ }
- return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos, $gtidSet );
- } else {
- return false;
+ $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
+ $row = $this->fetchObject( $res );
+ if ( $row && strlen( $row->Relay_Master_Log_File ) ) {
+ return new MySQLMasterPos(
+ "{$row->Relay_Master_Log_File}/{$row->Exec_Master_Log_Pos}",
+ $now
+ );
}
+
+ return false;
}
/**
* @return MySQLMasterPos|bool
*/
public function getMasterPos() {
- $res = $this->query( 'SHOW MASTER STATUS', __METHOD__ );
- $row = $this->fetchObject( $res );
+ $now = microtime( true );
- if ( $row ) {
- // Also fetch the last-written GTID set (MariaDB)
- if ( $this->useGTIDs ) {
- $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_binlog_pos'", __METHOD__ );
- $gtidRow = $this->fetchObject( $res );
- $gtidSet = $gtidRow ? $gtidRow->Value : '';
- } else {
- $gtidSet = '';
+ if ( $this->useGTIDs ) {
+ $res = $this->query( "SELECT @@global.gtid_binlog_pos AS Value", __METHOD__ );
+ $gtidRow = $this->fetchObject( $res );
+ if ( $gtidRow && strlen( $gtidRow->Value ) ) {
+ return new MySQLMasterPos( $gtidRow->Value, $now );
}
+ }
- return new MySQLMasterPos( $row->File, $row->Position, $gtidSet );
- } else {
- return false;
+ $res = $this->query( 'SHOW MASTER STATUS', __METHOD__ );
+ $row = $this->fetchObject( $res );
+ if ( $row && strlen( $row->File ) ) {
+ return new MySQLMasterPos( "{$row->File}/{$row->Position}", $now );
}
+
+ return false;
}
public function serverIsReadOnly() {
* @since 1.20
*/
public function lockIsFree( $lockName, $method ) {
+ if ( !parent::lockIsFree( $lockName, $method ) ) {
+ return false; // already held
+ }
+
$encName = $this->addQuotes( $this->makeLockName( $lockName ) );
$result = $this->query( "SELECT IS_FREE_LOCK($encName) AS lockstatus", $method );
$row = $this->fetchObject( $result );
*/
public function setBigSelects( $value = true ) {
if ( $value === 'default' ) {
- if ( $this->mDefaultBigSelects === null ) {
+ if ( $this->defaultBigSelects === null ) {
# Function hasn't been called before so it must already be set to the default
return;
} else {
- $value = $this->mDefaultBigSelects;
+ $value = $this->defaultBigSelects;
}
- } elseif ( $this->mDefaultBigSelects === null ) {
- $this->mDefaultBigSelects =
+ } elseif ( $this->defaultBigSelects === null ) {
+ $this->defaultBigSelects =
(bool)$this->selectField( false, '@@sql_big_selects', '', __METHOD__ );
}
$encValue = $value ? '1' : '0';
*/
public function listViews( $prefix = null, $fname = __METHOD__ ) {
// The name of the column containing the name of the VIEW
- $propertyName = 'Tables_in_' . $this->mDBname;
+ $propertyName = 'Tables_in_' . $this->dbName;
// Query for the VIEWS
$res = $this->query( 'SHOW FULL TABLES WHERE TABLE_TYPE = "VIEW"' );
return $index;
}
}
+
+ protected function isTransactableQuery( $sql ) {
+ return parent::isTransactableQuery( $sql ) &&
+ !preg_match( '/^SELECT\s+(GET|RELEASE|IS_FREE)_LOCK\(/', $sql );
+ }
}
class_alias( DatabaseMysqlBase::class, 'DatabaseMysqlBase' );
$mysqli = mysqli_init();
$connFlags = 0;
- if ( $this->mFlags & self::DBO_SSL ) {
+ if ( $this->flags & self::DBO_SSL ) {
$connFlags |= MYSQLI_CLIENT_SSL;
$mysqli->ssl_set(
$this->sslKeyPath,
$this->sslCiphers
);
}
- if ( $this->mFlags & self::DBO_COMPRESS ) {
+ if ( $this->flags & self::DBO_COMPRESS ) {
$connFlags |= MYSQLI_CLIENT_COMPRESS;
}
- if ( $this->mFlags & self::DBO_PERSISTENT ) {
+ if ( $this->flags & self::DBO_PERSISTENT ) {
$realServer = 'p:' . $realServer;
}
}
$mysqli->options( MYSQLI_OPT_CONNECT_TIMEOUT, 3 );
- if ( $mysqli->real_connect( $realServer, $this->mUser,
- $this->mPassword, $this->mDBname, $port, $socket, $connFlags )
+ if ( $mysqli->real_connect( $realServer, $this->user,
+ $this->password, $this->dbName, $port, $socket, $connFlags )
) {
return $mysqli;
}
* @return int
*/
function lastErrno() {
- if ( $this->mConn ) {
- return $this->mConn->errno;
+ if ( $this->conn ) {
+ return $this->conn->errno;
} else {
return mysqli_connect_errno();
}
function selectDB( $db ) {
$conn = $this->getBindingHandle();
- $this->mDBname = $db;
+ $this->dbName = $db;
return $conn->select_db( $db );
}
* @return string
*/
public function __toString() {
- if ( $this->mConn instanceof mysqli ) {
- return (string)$this->mConn->thread_id;
+ if ( $this->conn instanceof mysqli ) {
+ return (string)$this->conn->thread_id;
} else {
// mConn might be false or something.
- return (string)$this->mConn;
+ return (string)$this->conn;
}
}
}
);
}
- $this->mServer = $server;
- $this->mUser = $user;
- $this->mPassword = $password;
- $this->mDBname = $dbName;
+ $this->server = $server;
+ $this->user = $user;
+ $this->password = $password;
+ $this->dbName = $dbName;
$connectVars = [
// pg_connect() user $user as the default database. Since a database is *required*,
if ( (int)$this->port > 0 ) {
$connectVars['port'] = (int)$this->port;
}
- if ( $this->mFlags & self::DBO_SSL ) {
+ if ( $this->flags & self::DBO_SSL ) {
$connectVars['sslmode'] = 1;
}
try {
// Use new connections to let LoadBalancer/LBFactory handle reuse
- $this->mConn = pg_connect( $this->connectString, PGSQL_CONNECT_FORCE_NEW );
+ $this->conn = pg_connect( $this->connectString, PGSQL_CONNECT_FORCE_NEW );
} catch ( Exception $ex ) {
$this->restoreErrorHandler();
throw $ex;
$phpError = $this->restoreErrorHandler();
- if ( !$this->mConn ) {
+ if ( !$this->conn ) {
$this->queryLogger->debug(
"DB connection error\n" .
"Server: $server, Database: $dbName, User: $user, Password: " .
throw new DBConnectionError( $this, str_replace( "\n", ' ', $phpError ) );
}
- $this->mOpened = true;
+ $this->opened = true;
# If called from the command-line (e.g. importDump), only show errors
if ( $this->cliMode ) {
$this->query( "SET bytea_output = 'escape'", __METHOD__ ); // PHP bug 53127
}
- $this->determineCoreSchema( $this->mSchema );
+ $this->determineCoreSchema( $this->schema );
// The schema to be used is now in the search path; no need for explicit qualification
- $this->mSchema = '';
+ $this->schema = '';
- return $this->mConn;
+ return $this->conn;
}
public function databasesAreIndependent() {
* @throws DBUnexpectedError
*/
public function selectDB( $db ) {
- if ( $this->mDBname !== $db ) {
- return (bool)$this->open( $this->mServer, $this->mUser, $this->mPassword, $db );
+ if ( $this->dbName !== $db ) {
+ return (bool)$this->open( $this->server, $this->user, $this->password, $db );
} else {
return true;
}
}
protected function closeConnection() {
- return $this->mConn ? pg_close( $this->mConn ) : true;
+ return $this->conn ? pg_close( $this->conn ) : true;
+ }
+
+ protected function isTransactableQuery( $sql ) {
+ return parent::isTransactableQuery( $sql ) &&
+ !preg_match( '/^SELECT\s+pg_(try_|)advisory_\w+\(/', $sql );
}
public function doQuery( $sql ) {
}
}
/* Transaction stays in the ERROR state until rolled back */
- if ( $this->mTrxLevel ) {
+ if ( $this->trxLevel ) {
// Throw away the transaction state, then raise the error as normal.
// Note that if this connection is managed by LBFactory, it's already expected
// that the other transactions LBFactory manages will be rolled back.
}
public function lastError() {
- if ( $this->mConn ) {
+ if ( $this->conn ) {
if ( $this->mLastResult ) {
return pg_result_error( $this->mLastResult );
} else {
}
public function getDBname() {
- return $this->mDBname;
+ return $this->dbName;
}
public function getServer() {
- return $this->mServer;
+ return $this->server;
}
public function buildConcat( $stringList ) {
}
public function lockIsFree( $lockName, $method ) {
+ if ( !parent::lockIsFree( $lockName, $method ) ) {
+ return false; // already held
+ }
// http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
$key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
$result = $this->query( "SELECT (CASE(pg_try_advisory_lock($key))
protected $mLastResult;
/** @var PDO */
- protected $mConn;
+ protected $conn;
/** @var FSLockManager (hopefully on the same server as the DB) */
protected $lockMgr;
throw new InvalidArgumentException( "Need 'dbDirectory' or 'dbFilePath' parameter." );
} else {
$this->dbDir = $p['dbDirectory'];
- $this->mDBname = $p['dbname'];
- $lockDomain = $this->mDBname;
+ $this->dbName = $p['dbname'];
+ $lockDomain = $this->dbName;
// Stock wiki mode using standard file names per DB.
parent::__construct( $p );
// Super doesn't open when $user is false, but we can work with $dbName
$this->close();
$fileName = self::generateFileName( $this->dbDir, $dbName );
if ( !is_readable( $fileName ) ) {
- $this->mConn = false;
+ $this->conn = false;
throw new DBConnectionError( $this, "SQLite database not accessible" );
}
$this->openFile( $fileName );
- return (bool)$this->mConn;
+ return (bool)$this->conn;
}
/**
$this->dbPath = $fileName;
try {
- if ( $this->mFlags & self::DBO_PERSISTENT ) {
- $this->mConn = new PDO( "sqlite:$fileName", '', '',
+ if ( $this->flags & self::DBO_PERSISTENT ) {
+ $this->conn = new PDO( "sqlite:$fileName", '', '',
[ PDO::ATTR_PERSISTENT => true ] );
} else {
- $this->mConn = new PDO( "sqlite:$fileName", '', '' );
+ $this->conn = new PDO( "sqlite:$fileName", '', '' );
}
} catch ( PDOException $e ) {
$err = $e->getMessage();
}
- if ( !$this->mConn ) {
+ if ( !$this->conn ) {
$this->queryLogger->debug( "DB connection error: $err\n" );
throw new DBConnectionError( $this, $err );
}
- $this->mOpened = !!$this->mConn;
- if ( $this->mOpened ) {
+ $this->opened = !!$this->conn;
+ if ( $this->opened ) {
# Set error codes only, don't raise exceptions
- $this->mConn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
+ $this->conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
# Enforce LIKE to be case sensitive, just like MySQL
$this->query( 'PRAGMA case_sensitive_like = 1' );
- return $this->mConn;
+ return $this->conn;
}
return false;
* @return bool
*/
protected function closeConnection() {
- $this->mConn = null;
+ $this->conn = null;
return true;
}
* @return bool|ResultWrapper
*/
protected function doQuery( $sql ) {
- $res = $this->mConn->query( $sql );
+ $res = $this->conn->query( $sql );
if ( $res === false ) {
return false;
}
*/
function insertId() {
// PDO::lastInsertId yields a string :(
- return intval( $this->mConn->lastInsertId() );
+ return intval( $this->conn->lastInsertId() );
}
/**
* @return string
*/
function lastError() {
- if ( !is_object( $this->mConn ) ) {
+ if ( !is_object( $this->conn ) ) {
return "Cannot return last error, no db connection";
}
- $e = $this->mConn->errorInfo();
+ $e = $this->conn->errorInfo();
return isset( $e[2] ) ? $e[2] : '';
}
* @return string
*/
function lastErrno() {
- if ( !is_object( $this->mConn ) ) {
+ if ( !is_object( $this->conn ) ) {
return "Cannot return last error, no db connection";
} else {
- $info = $this->mConn->errorInfo();
+ $info = $this->conn->errorInfo();
return $info[1];
}
* @return string Version information from the database
*/
function getServerVersion() {
- $ver = $this->mConn->getAttribute( PDO::ATTR_SERVER_VERSION );
+ $ver = $this->conn->getAttribute( PDO::ATTR_SERVER_VERSION );
return $ver;
}
} else {
$this->query( 'BEGIN', $fname );
}
- $this->mTrxLevel = 1;
+ $this->trxLevel = 1;
}
/**
);
return "x'" . bin2hex( (string)$s ) . "'";
} else {
- return $this->mConn->quote( (string)$s );
+ return $this->conn->quote( (string)$s );
}
}
* @return string
*/
public function __toString() {
- return 'SQLite ' . (string)$this->mConn->getAttribute( PDO::ATTR_SERVER_VERSION );
+ return 'SQLite ' . (string)$this->conn->getAttribute( PDO::ATTR_SERVER_VERSION );
}
}
public function setSchemaVars( $vars );
/**
- * Check to see if a named lock is available (non-blocking)
+ * Check to see if a named lock is not locked by any thread (non-blocking)
*
* @param string $lockName Name of lock to poll
* @param string $method Name of method calling us
* 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 */
+ /** @var string|null Binlog file base name */
+ public $binlog;
+ /** @var int[]|null Binglog file position tuple */
public $pos;
/** @var string[] GTID list */
public $gtids = [];
public $asOfTime = 0.0;
/**
- * @param string $file Binlog file name
- * @param int $pos Binlog position
- * @param string $gtid Comma separated GTID set [optional]
+ * @param string $position One of (comma separated GTID list, <binlog file>/<integer>)
+ * @param float $asOfTime UNIX timestamp
*/
- function __construct( $file, $pos, $gtid = '' ) {
- $this->file = $file;
- $this->pos = $pos;
- $this->gtids = array_map( 'trim', explode( ',', $gtid ) );
- $this->asOfTime = microtime( true );
- }
+ public function __construct( $position, $asOfTime ) {
+ $m = [];
+ if ( preg_match( '!^(.+)\.(\d+)/(\d+)$!', $position, $m ) ) {
+ $this->binlog = $m[1]; // ideally something like host name
+ $this->pos = [ (int)$m[2], (int)$m[3] ];
+ } else {
+ $this->gtids = array_map( 'trim', explode( ',', $position ) );
+ if ( !$this->gtids ) {
+ throw new InvalidArgumentException( "GTID set should not be empty." );
+ }
+ }
- /**
- * @return string <binlog file>/<position>, e.g db1034-bin.000976/843431247
- */
- function __toString() {
- return "{$this->file}/{$this->pos}";
+ $this->asOfTime = $asOfTime;
}
- function asOfTime() {
+ public function asOfTime() {
return $this->asOfTime;
}
- function hasReached( DBMasterPos $pos ) {
+ public function hasReached( DBMasterPos $pos ) {
if ( !( $pos instanceof self ) ) {
throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
}
$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
+ $comparisons = [];
+ // Check that this has positions reaching those in $pos for all domains in common
foreach ( $thatPosByDomain as $domain => $thatPos ) {
- $thisPos = isset( $thisPosByDomain[$domain] ) ? $thisPosByDomain[$domain] : -1;
- $reached = $reached && ( $thatPos <= $thisPos );
+ if ( isset( $thisPosByDomain[$domain] ) ) {
+ $comparisons[] = ( $thatPos <= $thisPosByDomain[$domain] );
+ }
}
-
- return $reached;
+ // Check that $this has a GTID for at least one domain also in $pos; due to MariaDB
+ // quirks, prior master switch-overs may result in inactive garbage GTIDs that cannot
+ // be cleaned up. Assume that the domains in both this and $pos cover the relevant
+ // active channels.
+ return ( $comparisons && !in_array( false, $comparisons, true ) );
}
// Fallback to the binlog file comparisons
return false;
}
- function channelsMatch( DBMasterPos $pos ) {
+ public function channelsMatch( DBMasterPos $pos ) {
if ( !( $pos instanceof self ) ) {
throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
}
$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 );
+ // Check that $this has a GTID for at least one domain also in $pos; due to MariaDB
+ // quirks, prior master switch-overs may result in inactive garbage GTIDs that cannot
+ // easily be cleaned up. Assume that the domains in both this and $pos cover the
+ // relevant active channels.
+ return array_intersect( $thatPosDomains, $thisPosDomains ) ? true : false;
}
// Fallback to the binlog file comparisons
return ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] );
}
+ /**
+ * @return string|null
+ */
+ public function getLogFile() {
+ return $this->gtids ? null : "{$this->binlog}.{$this->pos[0]}";
+ }
+
+ /**
+ * @return string GTID set or <binlog file>/<position> (e.g db1034-bin.000976/843431247)
+ */
+ public function __toString() {
+ return $this->gtids
+ ? implode( ',', $this->gtids )
+ : $this->getLogFile() . "/{$this->pos[1]}";
+ }
+
/**
* @note: this returns false for multi-source replication GTID sets
* @see https://mariadb.com/kb/en/mariadb/gtid
* @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;
+ return ( $this->binlog !== null && $this->pos !== null )
+ ? [ 'binlog' => $this->binlog, 'pos' => $this->pos ]
+ : false;
}
}
public function getAnyOpenConnection( $i );
/**
- * Get a connection by index
+ * Get a connection handle by server index
*
* Avoid using CONN_TRX_AUTO with sqlite (e.g. check getServerType() first)
*
+ * If the caller uses $domain or sets CONN_TRX_AUTO in $flags, then it must also
+ * call ILoadBalancer::reuseConnection() on the handle when finished using it.
+ * In all other cases, this is not necessary, though not harmful either.
+ *
* @param int $i Server index or DB_MASTER/DB_REPLICA
* @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
* @param int $flags Bitfield of CONN_* class constants
*
+ * @note This method throws DBAccessError if ILoadBalancer::disable() was called
+ *
* @throws DBError
* @return Database
*/
*
* Avoid using CONN_TRX_AUTO with sqlite (e.g. check getServerType() first)
*
- * @note If disable() was called on this LoadBalancer, this method will throw a DBAccessError.
+ * If the caller uses $domain or sets CONN_TRX_AUTO in $flags, then it must also
+ * call ILoadBalancer::reuseConnection() on the handle when finished using it.
+ * In all other cases, this is not necessary, though not harmful either.
+ *
+ * @note This method throws DBAccessError if ILoadBalancer::disable() was called
*
* @param int $i Server index (does not support DB_MASTER/DB_REPLICA)
* @param string|bool $domain Domain ID, or false for the current domain
*/
class LoadBalancer implements ILoadBalancer {
/** @var array[] Map of (server index => server config array) */
- private $mServers;
+ private $servers;
/** @var Database[][][] Map of (connection category => server index => IDatabase[]) */
- private $mConns;
+ private $conns;
/** @var float[] Map of (server index => weight) */
- private $mLoads;
+ private $loads;
/** @var array[] Map of (group => server index => weight) */
- private $mGroupLoads;
+ private $groupLoads;
/** @var bool Whether to disregard replica DB lag as a factor in replica DB selection */
- private $mAllowLagged;
+ private $allowLagged;
/** @var int Seconds to spend waiting on replica DB lag to resolve */
private $waitTimeout;
/** @var array The LoadMonitor configuration */
/** @var Database DB connection object that caused a problem */
private $errorConnection;
/** @var int The generic (not query grouped) replica DB index (of $mServers) */
- private $mReadIndex;
+ private $readIndex;
/** @var bool|DBMasterPos False if not set */
- private $mWaitForPos;
+ private $waitForPos;
/** @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';
+ private $lastError = 'Unknown error';
/** @var string|bool Reason the LB is read-only or false if not */
private $readOnlyReason = false;
/** @var int Total connections opened */
if ( !isset( $params['servers'] ) ) {
throw new InvalidArgumentException( __CLASS__ . ': missing servers parameter' );
}
- $this->mServers = $params['servers'];
- foreach ( $this->mServers as $i => $server ) {
+ $this->servers = $params['servers'];
+ foreach ( $this->servers as $i => $server ) {
if ( $i == 0 ) {
- $this->mServers[$i]['master'] = true;
+ $this->servers[$i]['master'] = true;
} else {
- $this->mServers[$i]['replica'] = true;
+ $this->servers[$i]['replica'] = true;
}
}
? $params['waitTimeout']
: self::MAX_WAIT_DEFAULT;
- $this->mReadIndex = -1;
- $this->mConns = [
+ $this->readIndex = -1;
+ $this->conns = [
// Connection were transaction rounds may be applied
self::KEY_LOCAL => [],
self::KEY_FOREIGN_INUSE => [],
self::KEY_FOREIGN_INUSE_NOROUND => [],
self::KEY_FOREIGN_FREE_NOROUND => []
];
- $this->mLoads = [];
- $this->mWaitForPos = false;
- $this->mAllowLagged = false;
+ $this->loads = [];
+ $this->waitForPos = false;
+ $this->allowLagged = false;
if ( isset( $params['readOnlyReason'] ) && is_string( $params['readOnlyReason'] ) ) {
$this->readOnlyReason = $params['readOnlyReason'];
$this->loadMonitorConfig += [ 'lagWarnThreshold' => $this->maxLag ];
foreach ( $params['servers'] as $i => $server ) {
- $this->mLoads[$i] = $server['load'];
+ $this->loads[$i] = $server['load'];
if ( isset( $server['groupLoads'] ) ) {
foreach ( $server['groupLoads'] as $group => $ratio ) {
- if ( !isset( $this->mGroupLoads[$group] ) ) {
- $this->mGroupLoads[$group] = [];
+ if ( !isset( $this->groupLoads[$group] ) ) {
+ $this->groupLoads[$group] = [];
}
- $this->mGroupLoads[$group][$i] = $ratio;
+ $this->groupLoads[$group][$i] = $ratio;
}
}
}
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']
+ $maxServerLag = isset( $this->servers[$i]['max lag'] )
+ ? $this->servers[$i]['max lag']
: $this->maxLag; // default
# Constrain that futher by $maxLag argument
$maxServerLag = min( $maxServerLag, $maxLag );
}
public function getReaderIndex( $group = false, $domain = false ) {
- if ( count( $this->mServers ) == 1 ) {
+ if ( count( $this->servers ) == 1 ) {
// Skip the load balancing if there's only one server
return $this->getWriterIndex();
- } elseif ( $group === false && $this->mReadIndex >= 0 ) {
+ } elseif ( $group === false && $this->readIndex >= 0 ) {
// Shortcut if the generic reader index was already cached
- return $this->mReadIndex;
+ return $this->readIndex;
}
if ( $group !== false ) {
// Use the server weight array for this load group
- if ( isset( $this->mGroupLoads[$group] ) ) {
- $loads = $this->mGroupLoads[$group];
+ if ( isset( $this->groupLoads[$group] ) ) {
+ $loads = $this->groupLoads[$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" );
}
} else {
// Use the generic load group
- $loads = $this->mLoads;
+ $loads = $this->loads;
}
// Scale the configured load ratios according to each server's load and state
return false;
}
- if ( $this->mWaitForPos && $i != $this->getWriterIndex() ) {
+ if ( $this->waitForPos && $i != $this->getWriterIndex() ) {
// Before any data queries are run, wait for the server to catch up to the
// specified position. This is used to improve session consistency. Note that
// when LoadBalancer::waitFor() sets mWaitForPos, the waiting triggers here,
}
}
- if ( $this->mReadIndex <= 0 && $this->mLoads[$i] > 0 && $group === false ) {
+ if ( $this->readIndex <= 0 && $this->loads[$i] > 0 && $group === false ) {
// Cache the generic reader index for future ungrouped DB_REPLICA handles
- $this->mReadIndex = $i;
+ $this->readIndex = $i;
// Record if the generic reader index is in "lagged replica DB" mode
if ( $laggedReplicaMode ) {
$this->laggedReplicaMode = true;
// Quickly look through the available servers for a server that meets criteria...
$currentLoads = $loads;
while ( count( $currentLoads ) ) {
- if ( $this->mAllowLagged || $laggedReplicaMode ) {
+ if ( $this->allowLagged || $laggedReplicaMode ) {
$i = ArrayUtils::pickRandom( $currentLoads );
} else {
$i = false;
- if ( $this->mWaitForPos && $this->mWaitForPos->asOfTime() ) {
+ if ( $this->waitForPos && $this->waitForPos->asOfTime() ) {
// ChronologyProtecter sets mWaitForPos for session consistency.
// This triggers doWait() after connect, so it's especially good to
// avoid lagged servers so as to avoid excessive delay in that method.
- $ago = microtime( true ) - $this->mWaitForPos->asOfTime();
+ $ago = microtime( true ) - $this->waitForPos->asOfTime();
// Aim for <= 1 second of waiting (being too picky can backfire)
$i = $this->getRandomNonLagged( $currentLoads, $domain, $ago + 1 );
}
}
public function waitFor( $pos ) {
- $oldPos = $this->mWaitForPos;
+ $oldPos = $this->waitForPos;
try {
- $this->mWaitForPos = $pos;
+ $this->waitForPos = $pos;
// If a generic reader connection was already established, then wait now
- $i = $this->mReadIndex;
+ $i = $this->readIndex;
if ( $i > 0 ) {
if ( !$this->doWait( $i ) ) {
$this->laggedReplicaMode = true;
}
public function waitForOne( $pos, $timeout = null ) {
- $oldPos = $this->mWaitForPos;
+ $oldPos = $this->waitForPos;
try {
- $this->mWaitForPos = $pos;
+ $this->waitForPos = $pos;
- $i = $this->mReadIndex;
+ $i = $this->readIndex;
if ( $i <= 0 ) {
// Pick a generic replica DB if there isn't one yet
- $readLoads = $this->mLoads;
+ $readLoads = $this->loads;
unset( $readLoads[$this->getWriterIndex()] ); // replica DBs only
$readLoads = array_filter( $readLoads ); // with non-zero load
$i = ArrayUtils::pickRandom( $readLoads );
}
} finally {
# Restore the old position, as this is not used for lag-protection but for throttling
- $this->mWaitForPos = $oldPos;
+ $this->waitForPos = $oldPos;
}
return $ok;
public function waitForAll( $pos, $timeout = null ) {
$timeout = $timeout ?: $this->waitTimeout;
- $oldPos = $this->mWaitForPos;
+ $oldPos = $this->waitForPos;
try {
- $this->mWaitForPos = $pos;
- $serverCount = count( $this->mServers );
+ $this->waitForPos = $pos;
+ $serverCount = count( $this->servers );
$ok = true;
for ( $i = 1; $i < $serverCount; $i++ ) {
- if ( $this->mLoads[$i] > 0 ) {
+ if ( $this->loads[$i] > 0 ) {
$start = microtime( true );
$ok = $this->doWait( $i, true, $timeout ) && $ok;
$timeout -= ( microtime( true ) - $start );
}
} finally {
# Restore the old position, as this is not used for lag-protection but for throttling
- $this->mWaitForPos = $oldPos;
+ $this->waitForPos = $oldPos;
}
return $ok;
return;
}
- if ( !$this->mWaitForPos || $pos->hasReached( $this->mWaitForPos ) ) {
- $this->mWaitForPos = $pos;
+ if ( !$this->waitForPos || $pos->hasReached( $this->waitForPos ) ) {
+ $this->waitForPos = $pos;
}
}
* @return IDatabase|bool
*/
public function getAnyOpenConnection( $i ) {
- foreach ( $this->mConns as $connsByServer ) {
+ foreach ( $this->conns as $connsByServer ) {
if ( !empty( $connsByServer[$i] ) ) {
/** @var IDatabase[] $serverConns */
$serverConns = $connsByServer[$i];
$knownReachedPos = $this->srvCache->get( $key );
if (
$knownReachedPos instanceof DBMasterPos &&
- $knownReachedPos->hasReached( $this->mWaitForPos )
+ $knownReachedPos->hasReached( $this->waitForPos )
) {
$this->replLogger->debug(
__METHOD__ .
[ 'dbserver' => $server ]
);
- $result = $conn->masterPosWait( $this->mWaitForPos, $timeout );
+ $result = $conn->masterPosWait( $this->waitForPos, $timeout );
if ( $result === null ) {
$this->replLogger->warning(
__METHOD__ . ': Errored out waiting on {host} pos {pos}',
[
'host' => $server,
- 'pos' => $this->mWaitForPos,
+ 'pos' => $this->waitForPos,
'trace' => ( new RuntimeException() )->getTraceAsString()
]
);
__METHOD__ . ': Timed out waiting on {host} pos {pos}',
[
'host' => $server,
- 'pos' => $this->mWaitForPos,
+ 'pos' => $this->waitForPos,
'trace' => ( new RuntimeException() )->getTraceAsString()
]
);
$this->replLogger->info( __METHOD__ . ": Done" );
$ok = true;
// Remember that the DB reached this point
- $this->srvCache->set( $key, $this->mWaitForPos, BagOStuff::TTL_DAY );
+ $this->srvCache->set( $key, $this->waitForPos, BagOStuff::TTL_DAY );
}
if ( $close ) {
# Operation-based index
if ( $i == self::DB_REPLICA ) {
- $this->mLastError = 'Unknown error'; // reset error string
+ $this->lastError = 'Unknown error'; // reset error string
# Try the general server pool if $groups are unavailable.
$i = ( $groups === [ false ] )
? 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;
+ $this->lastError = 'No working replica DB server: ' . $this->lastError;
// Throw an exception
$this->reportConnectionError();
return null; // not reached
}
$domain = $conn->getDomainID();
- if ( !isset( $this->mConns[$connInUseKey][$serverIndex][$domain] ) ) {
+ if ( !isset( $this->conns[$connInUseKey][$serverIndex][$domain] ) ) {
throw new InvalidArgumentException( __METHOD__ .
": connection $serverIndex/$domain not found; it may have already been freed." );
- } elseif ( $this->mConns[$connInUseKey][$serverIndex][$domain] !== $conn ) {
+ } elseif ( $this->conns[$connInUseKey][$serverIndex][$domain] !== $conn ) {
throw new InvalidArgumentException( __METHOD__ .
": connection $serverIndex/$domain mismatched; it may have already been freed." );
}
$conn->setLBInfo( 'foreignPoolRefCount', --$refCount );
if ( $refCount <= 0 ) {
- $this->mConns[$connFreeKey][$serverIndex][$domain] = $conn;
- unset( $this->mConns[$connInUseKey][$serverIndex][$domain] );
- if ( !$this->mConns[$connInUseKey][$serverIndex] ) {
- unset( $this->mConns[$connInUseKey][$serverIndex] ); // clean up
+ $this->conns[$connFreeKey][$serverIndex][$domain] = $conn;
+ unset( $this->conns[$connInUseKey][$serverIndex][$domain] );
+ if ( !$this->conns[$connInUseKey][$serverIndex] ) {
+ unset( $this->conns[$connInUseKey][$serverIndex] ); // clean up
}
$this->connLogger->debug( __METHOD__ . ": freed connection $serverIndex/$domain" );
} else {
} else {
// Connection is to the local domain
$connKey = $autoCommit ? self::KEY_LOCAL_NOROUND : self::KEY_LOCAL;
- if ( isset( $this->mConns[$connKey][$i][0] ) ) {
- $conn = $this->mConns[$connKey][$i][0];
+ if ( isset( $this->conns[$connKey][$i][0] ) ) {
+ $conn = $this->conns[$connKey][$i][0];
} else {
- if ( !isset( $this->mServers[$i] ) || !is_array( $this->mServers[$i] ) ) {
+ if ( !isset( $this->servers[$i] ) || !is_array( $this->servers[$i] ) ) {
throw new InvalidArgumentException( "No server with index '$i'." );
}
// Open a new connection
- $server = $this->mServers[$i];
+ $server = $this->servers[$i];
$server['serverIndex'] = $i;
$server['autoCommitOnly'] = $autoCommit;
if ( $this->localDomain->getDatabase() !== null ) {
$host = $this->getServerName( $i );
if ( $conn->isOpen() ) {
$this->connLogger->debug( "Connected to database $i at '$host'." );
- $this->mConns[$connKey][$i][0] = $conn;
+ $this->conns[$connKey][$i][0] = $conn;
} else {
$this->connLogger->warning( "Failed to connect to database $i at '$host'." );
$this->errorConnection = $conn;
$connInUseKey = self::KEY_FOREIGN_INUSE;
}
- if ( isset( $this->mConns[$connInUseKey][$i][$domain] ) ) {
+ if ( isset( $this->conns[$connInUseKey][$i][$domain] ) ) {
// Reuse an in-use connection for the same domain
- $conn = $this->mConns[$connInUseKey][$i][$domain];
+ $conn = $this->conns[$connInUseKey][$i][$domain];
$this->connLogger->debug( __METHOD__ . ": reusing connection $i/$domain" );
- } elseif ( isset( $this->mConns[$connFreeKey][$i][$domain] ) ) {
+ } elseif ( isset( $this->conns[$connFreeKey][$i][$domain] ) ) {
// Reuse a free connection for the same domain
- $conn = $this->mConns[$connFreeKey][$i][$domain];
- unset( $this->mConns[$connFreeKey][$i][$domain] );
- $this->mConns[$connInUseKey][$i][$domain] = $conn;
+ $conn = $this->conns[$connFreeKey][$i][$domain];
+ unset( $this->conns[$connFreeKey][$i][$domain] );
+ $this->conns[$connInUseKey][$i][$domain] = $conn;
$this->connLogger->debug( __METHOD__ . ": reusing free connection $i/$domain" );
- } elseif ( !empty( $this->mConns[$connFreeKey][$i] ) ) {
+ } elseif ( !empty( $this->conns[$connFreeKey][$i] ) ) {
// Reuse a free connection from another domain
- $conn = reset( $this->mConns[$connFreeKey][$i] );
- $oldDomain = key( $this->mConns[$connFreeKey][$i] );
+ $conn = reset( $this->conns[$connFreeKey][$i] );
+ $oldDomain = key( $this->conns[$connFreeKey][$i] );
if ( strlen( $dbName ) && !$conn->selectDB( $dbName ) ) {
- $this->mLastError = "Error selecting database '$dbName' on server " .
+ $this->lastError = "Error selecting database '$dbName' on server " .
$conn->getServer() . " from client host {$this->host}";
$this->errorConnection = $conn;
$conn = false;
} else {
$conn->tablePrefix( $prefix );
- unset( $this->mConns[$connFreeKey][$i][$oldDomain] );
+ unset( $this->conns[$connFreeKey][$i][$oldDomain] );
// Note that if $domain is an empty string, getDomainID() might not match it
- $this->mConns[$connInUseKey][$i][$conn->getDomainId()] = $conn;
+ $this->conns[$connInUseKey][$i][$conn->getDomainId()] = $conn;
$this->connLogger->debug( __METHOD__ .
": reusing free connection from $oldDomain for $domain" );
}
} else {
- if ( !isset( $this->mServers[$i] ) || !is_array( $this->mServers[$i] ) ) {
+ if ( !isset( $this->servers[$i] ) || !is_array( $this->servers[$i] ) ) {
throw new InvalidArgumentException( "No server with index '$i'." );
}
// Open a new connection
- $server = $this->mServers[$i];
+ $server = $this->servers[$i];
$server['serverIndex'] = $i;
$server['foreignPoolRefCount'] = 0;
$server['foreign'] = true;
} else {
$conn->tablePrefix( $prefix ); // as specified
// Note that if $domain is an empty string, getDomainID() might not match it
- $this->mConns[$connInUseKey][$i][$conn->getDomainID()] = $conn;
+ $this->conns[$connInUseKey][$i][$conn->getDomainID()] = $conn;
$this->connLogger->debug( __METHOD__ . ": opened new connection for $i/$domain" );
}
}
$conn = $this->errorConnection; // the connection which caused the error
$context = [
'method' => __METHOD__,
- 'last_error' => $this->mLastError,
+ 'last_error' => $this->lastError,
];
if ( $conn instanceof IDatabase ) {
);
// throws DBConnectionError
- $conn->reportConnectionError( "{$this->mLastError} ({$context['db_server']})" );
+ $conn->reportConnectionError( "{$this->lastError} ({$context['db_server']})" );
} else {
// No last connection, probably due to all servers being too busy
$this->connLogger->error(
);
// If all servers were busy, mLastError will contain something sensible
- throw new DBConnectionError( null, $this->mLastError );
+ throw new DBConnectionError( null, $this->lastError );
}
}
}
public function haveIndex( $i ) {
- return array_key_exists( $i, $this->mServers );
+ return array_key_exists( $i, $this->servers );
}
public function isNonZeroLoad( $i ) {
- return array_key_exists( $i, $this->mServers ) && $this->mLoads[$i] != 0;
+ return array_key_exists( $i, $this->servers ) && $this->loads[$i] != 0;
}
public function getServerCount() {
- return count( $this->mServers );
+ return count( $this->servers );
}
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'];
+ if ( isset( $this->servers[$i]['hostName'] ) ) {
+ $name = $this->servers[$i]['hostName'];
+ } elseif ( isset( $this->servers[$i]['host'] ) ) {
+ $name = $this->servers[$i]['host'];
} else {
$name = '';
}
}
public function getServerType( $i ) {
- return isset( $this->mServers[$i]['type'] ) ? $this->mServers[$i]['type'] : 'unknown';
+ return isset( $this->servers[$i]['type'] ) ? $this->servers[$i]['type'] : 'unknown';
}
public function getMasterPos() {
# 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 );
+ $serverCount = count( $this->servers );
for ( $i = 1; $i < $serverCount; $i++ ) {
$conn = $this->getAnyOpenConnection( $i );
if ( $conn ) {
$conn->close();
} );
- $this->mConns = [
+ $this->conns = [
self::KEY_LOCAL => [],
self::KEY_FOREIGN_INUSE => [],
self::KEY_FOREIGN_FREE => [],
public function closeConnection( IDatabase $conn ) {
$serverIndex = $conn->getLBInfo( 'serverIndex' ); // second index level of mConns
- foreach ( $this->mConns as $type => $connsByServer ) {
+ foreach ( $this->conns as $type => $connsByServer ) {
if ( !isset( $connsByServer[$serverIndex] ) ) {
continue;
}
if ( $conn === $trackedConn ) {
$host = $this->getServerName( $i );
$this->connLogger->debug( "Closing connection to database $i at '$host'." );
- unset( $this->mConns[$type][$serverIndex][$i] );
+ unset( $this->conns[$type][$serverIndex][$i] );
--$this->connsOpened;
break 2;
}
public function allowLagged( $mode = null ) {
if ( $mode === null ) {
- return $this->mAllowLagged;
+ return $this->allowLagged;
}
- $this->mAllowLagged = $mode;
+ $this->allowLagged = $mode;
- return $this->mAllowLagged;
+ return $this->allowLagged;
}
public function pingAll() {
}
public function forEachOpenConnection( $callback, array $params = [] ) {
- foreach ( $this->mConns as $connsByServer ) {
+ foreach ( $this->conns as $connsByServer ) {
foreach ( $connsByServer as $serverConns ) {
foreach ( $serverConns as $conn ) {
$mergedParams = array_merge( [ $conn ], $params );
public function forEachOpenMasterConnection( $callback, array $params = [] ) {
$masterIndex = $this->getWriterIndex();
- foreach ( $this->mConns as $connsByServer ) {
+ foreach ( $this->conns as $connsByServer ) {
if ( isset( $connsByServer[$masterIndex] ) ) {
/** @var IDatabase $conn */
foreach ( $connsByServer[$masterIndex] as $conn ) {
}
public function forEachOpenReplicaConnection( $callback, array $params = [] ) {
- foreach ( $this->mConns as $connsByServer ) {
+ foreach ( $this->conns as $connsByServer ) {
foreach ( $connsByServer as $i => $serverConns ) {
if ( $i === $this->getWriterIndex() ) {
continue; // skip master
$lagTimes = $this->getLagTimes( $domain );
foreach ( $lagTimes as $i => $lag ) {
- if ( $this->mLoads[$i] > 0 && $lag > $maxLag ) {
+ if ( $this->loads[$i] > 0 && $lag > $maxLag ) {
$maxLag = $lag;
- $host = $this->mServers[$i]['host'];
+ $host = $this->servers[$i]['host'];
$maxIndex = $i;
}
}
$knownLagTimes = []; // map of (server index => 0 seconds)
$indexesWithLag = [];
- foreach ( $this->mServers as $i => $server ) {
+ foreach ( $this->servers as $i => $server ) {
if ( empty( $server['is static'] ) ) {
$indexesWithLag[] = $i; // DB server might have replication lag
} else {
# Render printable version, use printable version cache
if ( $outputPage->isPrintable() ) {
$parserOptions->setIsPrintable( true );
- $parserOptions->setEditSection( false );
$poOptions['enableSectionEditLinks'] = false;
} elseif ( $this->disableSectionEditForRender
|| !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user )
) {
- $parserOptions->setEditSection( false );
$poOptions['enableSectionEditLinks'] = false;
}
public function render() {
$this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
$this->getContext()->getOutput()->setArticleBodyOnly( true );
- $this->getContext()->getOutput()->enableSectionEditLinks( false );
$this->disableSectionEditForRender = true;
$this->view();
}
# Inhibit editsection links if requested in the page
if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
- $maybeShowEditLink = $showEditLink = false;
+ $maybeShowEditLink = false;
} else {
- $maybeShowEditLink = true; /* Actual presence will depend on ParserOptions option */
- $showEditLink = $this->mOptions->getEditSection();
- }
- if ( $showEditLink ) {
- $this->mOutput->setEditSectionTokens( true );
+ $maybeShowEditLink = true; /* Actual presence will depend on post-cache transforms */
}
# Get all headlines for numbering them and adding funky stuff like [edit]
* $this : caller
* $section : the section number
* &$sectionContent : ref to the content of the section
- * $showEditLinks : boolean describing whether this section has an edit link
+ * $maybeShowEditLinks : boolean describing whether this section has an edit link
*/
- Hooks::run( 'ParserSectionCreate', [ $this, $i, &$sections[$i], $showEditLink ] );
+ Hooks::run( 'ParserSectionCreate', [ $this, $i, &$sections[$i], $maybeShowEditLink ] );
$i++;
}
wfDebug( "ParserOutput cache found.\n" );
- // The edit section preference may not be the appropiate one in
- // the ParserOutput, as we are not storing it in the parsercache
- // key. Force it here. See T33445.
- $value->setEditSectionTokens( $popts->getEditSection() );
-
$wikiPage = method_exists( $article, 'getPage' )
? $article->getPage()
: $article;
*/
private $mTimestamp;
- /**
- * The edit section flag is in ParserOptions for historical reasons, but
- * doesn't actually affect the parser output since Feb 2015.
- * @var bool
- */
- private $mEditSection = true;
-
/**
* Stored user object
* @var User
* @return bool
*/
public function getEditSection() {
- return $this->mEditSection;
+ wfDeprecated( __METHOD__, '1.31' );
+ return true;
}
/**
* @return bool Old value
*/
public function setEditSection( $x ) {
- return wfSetVar( $this->mEditSection, $x );
+ wfDeprecated( __METHOD__, '1.31' );
+ return true;
}
/**
$defaults = self::getCanonicalOverrides() + self::getDefaults();
$inCacheKey = self::$inCacheKey;
- // Historical hack: 'editsection' hasn't been a true parser option since
- // Feb 2015 (instead the parser outputs a constant placeholder and post-parse
- // processing handles the option). But Wikibase forces it in $forOptions
- // and expects the cache key to still vary on it for T85252.
- // @deprecated since 1.30, Wikibase should use addExtraKey() or something instead.
- if ( in_array( 'editsection', $forOptions, true ) ) {
- $options['editsection'] = $this->mEditSection;
- $defaults['editsection'] = true;
- $inCacheKey['editsection'] = true;
- ksort( $inCacheKey );
- }
-
// We only include used options with non-canonical values in the key
// so adding a new option doesn't invalidate the entire parser cache.
// The drawback to this is that changing the default value of an option
*/
public $mSections = [];
- /**
- * @deprecated since 1.31 Use getText() options.
- * @var bool $mEditSectionTokens prefix/suffix markers if edit sections were output as tokens.
- */
- public $mEditSectionTokens = true;
-
/**
* @var array $mProperties Name/value pairs to be cached in the DB.
*/
*/
public $mTimestamp;
- /**
- * @deprecated since 1.31 Use getText() options.
- * @var bool $mTOCEnabled Whether TOC should be shown, can't override __NOTOC__.
- */
- public $mTOCEnabled = true;
-
/**
* @var bool $mEnableOOUI Whether OOUI should be enabled.
*/
* @return string HTML
*/
public function getText( $options = [] ) {
- if ( !array_key_exists( 'allowTOC', $options ) && empty( $this->mTOCEnabled ) ) {
- wfDeprecated( 'ParserOutput stateful allowTOC', '1.31' );
- }
-
- // Note that while $this->mEditSectionTokens formerly defaulted to false,
- // ParserOptions->getEditSection() defaults to true and Parser copies
- // that to us so true makes more sense as the stateless default.
- if ( !array_key_exists( 'enableSectionEditLinks', $options ) && !$this->mEditSectionTokens ) {
- wfDeprecated( 'ParserOutput stateful enableSectionEditLinks', '1.31' );
- }
-
$options += [
- // empty() here because old cached versions might lack the field somehow.
- // In that situation, the historical behavior (possibly buggy) is to remove the TOC.
- 'allowTOC' => !empty( $this->mTOCEnabled ),
- 'enableSectionEditLinks' => $this->mEditSectionTokens,
+ 'allowTOC' => true,
+ 'enableSectionEditLinks' => true,
'unwrap' => false,
'deduplicateStyles' => true,
];
* @deprecated since 1.31 Use getText() options.
*/
public function getEditSectionTokens() {
- return $this->mEditSectionTokens;
+ wfDeprecated( __METHOD__, '1.31' );
+ return true;
}
public function &getLinks() {
* @deprecated since 1.31 Use getText() options.
*/
public function getTOCEnabled() {
- return $this->mTOCEnabled;
+ wfDeprecated( __METHOD__, '1.31' );
+ return true;
}
public function getEnableOOUI() {
* @deprecated since 1.31 Use getText() options.
*/
public function setEditSectionTokens( $t ) {
- return wfSetVar( $this->mEditSectionTokens, $t );
+ wfDeprecated( __METHOD__, '1.31' );
+ return true;
}
public function setIndexPolicy( $policy ) {
* @deprecated since 1.31 Use getText() options.
*/
public function setTOCEnabled( $flag ) {
- return wfSetVar( $this->mTOCEnabled, $flag );
+ wfDeprecated( __METHOD__, '1.31' );
+ return true;
}
public function addCategory( $c, $sort ) {
return 'local';
}
- /**
- * From where in the page HTML should this module be loaded?
- *
- * @deprecated since 1.29 Obsolete. All modules load async from `<head>`.
- * @return string
- */
- public function getPosition() {
- return 'top';
- }
-
/**
* Whether this module's JS expects to work without the client-side ResourceLoader module.
* Returning true from this function will prevent mw.loader.state() call from being
}
}
- /**
- * Get a FormOptions object containing the default options
- *
- * @return FormOptions
- */
- public function getDefaultOptions() {
- $opts = parent::getDefaultOptions();
-
- $opts->add( 'categories', '' );
- $opts->add( 'categories_any', false );
-
- return $opts;
- }
-
/**
* Get all custom filters
*
$join_conds
);
- // Build the final data
- if ( $this->getConfig()->get( 'AllowCategorizedRecentChanges' ) ) {
- $this->filterByCategories( $rows, $opts );
- }
-
return $rows;
}
*/
function getExtraOptions( $opts ) {
$opts->consumeValues( [
- 'namespace', 'invert', 'associated', 'tagfilter', 'categories', 'categories_any'
+ 'namespace', 'invert', 'associated', 'tagfilter'
] );
$extraOpts = [];
$extraOpts['namespace'] = $this->namespaceFilterForm( $opts );
- if ( $this->getConfig()->get( 'AllowCategorizedRecentChanges' ) ) {
- $extraOpts['category'] = $this->categoryFilterForm( $opts );
- }
-
$tagFilter = ChangeTags::buildTagFilterSelector(
$opts['tagfilter'], false, $this->getContext() );
if ( count( $tagFilter ) ) {
return [ $nsLabel, "$nsSelect $invert $associated" ];
}
- /**
- * Create an input to filter changes by categories
- *
- * @param FormOptions $opts
- * @return array
- */
- protected function categoryFilterForm( FormOptions $opts ) {
- list( $label, $input ) = Xml::inputLabelSep( $this->msg( 'rc_categories' )->text(),
- 'categories', 'mw-categories', false, $opts['categories'] );
-
- $input .= ' ' . Xml::checkLabel( $this->msg( 'rc_categories_any' )->text(),
- 'categories_any', 'mw-categories_any', $opts['categories_any'] );
-
- return [ $label, $input ];
- }
-
/**
* Filter $rows by categories set in $opts
*
+ * @deprecated since 1.31
+ *
* @param ResultWrapper &$rows Database rows
* @param FormOptions $opts
*/
function filterByCategories( &$rows, FormOptions $opts ) {
+ wfDeprecated( __METHOD__, '1.31' );
+
$categories = array_map( 'trim', explode( '|', $opts['categories'] ) );
if ( !count( $categories ) ) {
if ( ( $this->mPreview || !$isText ) && $content ) {
// NOTE: non-text content has no source view, so always use rendered preview
- // Hide [edit]s
$popts = $out->parserOptions();
- $popts->setEditSection( false );
$pout = $content->getParserOutput( $this->mTargetObj, $rev->getId(), $popts, true );
$out->addParserOutput( $pout, [
*/
use Wikimedia\Rdbms\IDatabase;
+use MediaWiki\MediaWikiServices;
/**
* Represents a "user group membership" -- a specific instance of a user belonging
}
// Purge old, expired memberships from the DB
- self::purgeExpired( $dbw );
+ JobQueueGroup::singleton()->push( new UserGroupExpiryJob() );
// Check that the values make sense
if ( $this->group === null ) {
/**
* Purge expired memberships from the user_groups table
- *
- * @param IDatabase|null $dbw
*/
- public static function purgeExpired( IDatabase $dbw = null ) {
- if ( wfReadOnly() ) {
+ public static function purgeExpired() {
+ $services = MediaWikiServices::getInstance();
+ if ( $services->getReadOnlyMode()->isReadOnly() ) {
return;
}
- if ( $dbw === null ) {
- $dbw = wfGetDB( DB_MASTER );
- }
+ $lbFactory = $services->getDBLoadBalancerFactory();
+ $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
+ $dbw = $services->getDBLoadBalancer()->getConnection( DB_MASTER );
- DeferredUpdates::addUpdate( new AtomicSectionUpdate(
- $dbw,
- __METHOD__,
- function ( IDatabase $dbw, $fname ) {
- $expiryCond = [ 'ug_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ];
- $res = $dbw->select( 'user_groups', self::selectFields(), $expiryCond, $fname );
+ $lockKey = $dbw->getDomainID() . ':usergroups-prune'; // specific to this wiki
+ $scopedLock = $dbw->getScopedLockAndFlush( $lockKey, __METHOD__, 0 );
+ if ( !$scopedLock ) {
+ return; // already running
+ }
- // save an array of users/groups to insert to user_former_groups
- $usersAndGroups = [];
+ $now = time();
+ do {
+ $dbw->startAtomic( __METHOD__ );
+
+ $res = $dbw->select(
+ 'user_groups',
+ self::selectFields(),
+ [ 'ug_expiry < ' . $dbw->addQuotes( $dbw->timestamp( $now ) ) ],
+ __METHOD__,
+ [ 'FOR UPDATE', 'LIMIT' => 100 ]
+ );
+
+ if ( $res->numRows() > 0 ) {
+ $insertData = []; // array of users/groups to insert to user_former_groups
+ $deleteCond = []; // array for deleting the rows that are to be moved around
foreach ( $res as $row ) {
- $usersAndGroups[] = [ 'ufg_user' => $row->ug_user, 'ufg_group' => $row->ug_group ];
+ $insertData[] = [ 'ufg_user' => $row->ug_user, 'ufg_group' => $row->ug_group ];
+ $deleteCond[] = $dbw->makeList(
+ [ 'ug_user' => $row->ug_user, 'ug_group' => $row->ug_group ],
+ $dbw::LIST_AND
+ );
}
+ // Delete the rows we're about to move
+ $dbw->delete(
+ 'user_groups',
+ $dbw->makeList( $deleteCond, $dbw::LIST_OR ),
+ __METHOD__
+ );
+ // Push the groups to user_former_groups
+ $dbw->insert( 'user_former_groups', $insertData, __METHOD__, [ 'IGNORE' ] );
+ }
- // delete 'em all
- $dbw->delete( 'user_groups', $expiryCond, $fname );
+ $dbw->endAtomic( __METHOD__ );
- // and push the groups to user_former_groups
- $dbw->insert( 'user_former_groups', $usersAndGroups, __METHOD__, [ 'IGNORE' ] );
- }
- ) );
+ $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
+ } while ( $res->numRows() > 0 );
}
/**
"rollback-success": "Revertíes les ediciones de {{GENDER:$3|$1}}; devueltu a la última revisión de {{GENDER:$4|$2}}.",
"rollback-success-notify": "Revertíes les ediciones de $1 a la última revisión de $2. [$3 Ver cambeos]",
"sessionfailure-title": "Fallu de sesión",
- "sessionfailure": "Paez qu'hai un problema col aniciu de sesión;\natayóse esta aición por precaución escontra secuestru de sesiones.\nTorna a la páxina anterior, recarga esa páxina y vuelve a tentalo.",
+ "sessionfailure": "Paez qu'hai un problema col aniciu de sesión;\natayóse esta aición por precaución escontra secuestru de sesiones.\nUnvia'l formulariu otra vegada.",
"changecontentmodel": "Cambiar el modelu de conteníu d'una páxina",
"changecontentmodel-legend": "Cambiar el modelu de conteníu",
"changecontentmodel-title-label": "Títulu de la páxina",
"watchlistedit-clear-titles": "Títulos:",
"watchlistedit-clear-submit": "Llimpiar la llista de siguimientu (¡Esto ye permanente!)",
"watchlistedit-clear-done": "Llimpióse la to llista de siguimientu.",
+ "watchlistedit-clear-jobqueue": "Ta llimpiándose la llista de siguimientu. ¡Esta aición puede tardar daqué de tiempu!",
"watchlistedit-clear-removed": "{{PLURAL:$1|Desanicióse 1 títulu|Desaniciáronse $1 títulos}}:",
"watchlistedit-too-many": "Hai demasiaes páxines p'amosales equí.",
"watchlisttools-clear": "Llimpiar la llista de siguimientu",
"right-bigdelete": "Выдаленьне старонак зь вялікімі гісторыямі",
"right-deletelogentry": "выдаленьне і аднаўленьне асобных запісаў журналу",
"right-deleterevision": "выдаленьне і аднаўленьне асобных вэрсіяў старонак",
- "right-deletedhistory": "пÑ\80аглÑ\8fд вÑ\8bдаленай гÑ\96Ñ\81Ñ\82оÑ\80Ñ\8bÑ\96 Ñ\81Ñ\82аÑ\80онак без доступу да выдаленага тэксту",
- "right-deletedtext": "прагляд выдаленага тэксту і зьменаў паміж выдаленымі вэрсіямі старонак",
+ "right-deletedhistory": "Ð\9fÑ\80аглÑ\8fд вÑ\8bдаленай гÑ\96Ñ\81Ñ\82оÑ\80Ñ\8bÑ\96 Ñ\81Ñ\82аÑ\80онак бÑ\8fз доступу да выдаленага тэксту",
+ "right-deletedtext": "Ð\9fрагляд выдаленага тэксту і зьменаў паміж выдаленымі вэрсіямі старонак",
"right-browsearchive": "пошук выдаленых старонак",
"right-undelete": "аднаўленьне старонак",
"right-suppressrevision": "праглядаць, хаваць і аднаўляць пэўныя вэрсіі старонак, зробленыя любым удзельнікам",
"log-title-wildcard": "Αναζήτησε τίτλους που αρχίζουν με αυτό το κείμενο",
"showhideselectedlogentries": "Αλλαγή ορατότητας των επιλεγμένων καταχωρήσεων στο αρχείο καταγραφής συμβάντων",
"log-edit-tags": "Επεξεργασία ετικετών των επιλεγμένων καταχωρήσεων του αρχείου καταγραφής",
+ "checkbox-select": "Επιλογή: $1",
"checkbox-all": "Όλα",
"checkbox-none": "Κανένα",
"checkbox-invert": "Αντιστροφή",
"newimages-user": "Διεύθυνση IP ή όνομα χρήστη",
"newimages-showbots": "Εμφάνιση αρχείων ανεβασμένων από ρομπότ",
"newimages-hidepatrolled": "Απόκρυψη ελεγμένων αρχείων.",
+ "newimages-mediatype": "Τύπος μέσου:",
"noimages": "Δεν υπάρχουν εικόνες.",
"ilsubmit": "Αναζήτηση",
"bydate": "ημερομηνίας",
"unpatrolledletter": "!",
"number_of_watching_users_RCview": "[$1]",
"number_of_watching_users_pageview": "[$1 watching {{PLURAL:$1|user|users}}]",
- "rc_categories": "Limit to categories (separate with \"|\"):",
- "rc_categories_any": "Any of the chosen",
"rc-change-size": "$1",
"rc-change-size-new": "$1 {{PLURAL:$1|byte|bytes}} after change",
"newsectionsummary": "/* $1 */ new section",
"tog-hidepatrolled": "Ocultar ediciones patrulladas de los cambios recientes",
"tog-newpageshidepatrolled": "Ocultar páginas patrulladas de la lista de páginas nuevas",
"tog-showtoolbar": "Mostrar barra de edición",
+ "tog-oldsig": "Su firma actual:",
"tog-ccmeonemails": "Enviarme una copia de los correos electrónicos que yo envíe a otros usuarios",
"sunday": "domingo",
"monday": "lunes",
"views": "Vistas",
"toolbox": "Herramientas",
"otherlanguages": "Otros idiomas",
- "lastmodifiedat": "Esta página fue modificada por última vez el $1, a las $2.",
+ "lastmodifiedat": "Esta página fue editada por última vez el $1, a las $2.",
"protectedpage": "Página protegida",
"jumpto": "Saltar a:",
"jumptonavigation": "navegación",
"jumptosearch": "buscar",
"view-pool-error": "Lo sentimos, los servidores están sobrecargados en este momento.\nDemasiados usuarios están intentando ver esta página.\nPor favor espere unos instantes antes de reintentar acceder nuevamente a esta página.\n\n$1",
+ "generic-pool-error": "Lo sentimos, los servidores están sobrecargados en este momento.\nHay demasiados usuarios tratando de ver este recurso.\nPor favor espere un momento antes de intentar acceder de nuevo.",
"aboutsite": "Acerca de {{SITENAME}}",
"aboutpage": "Project:Acerca de",
"currentevents": "Actualidad",
"ok": "Aceptar",
"retrievedfrom": "Obtenido de «$1»",
"youhavenewmessages": "{{PLURAL:$3|Tiene}} $1 ($2).",
- "youhavenewmessagesmulti": "Tienes mensajes nuevos en $1",
+ "youhavenewmessagesfromusers": "{{PLURAL:$4|Tiene}} $1 de {{PLURAL:$3|otro usuario|$3 usuarios}} ($2).",
+ "youhavenewmessagesmanyusers": "Tiene $1 de muchos usuarios ($2).",
+ "youhavenewmessagesmulti": "Tiene mensajes nuevos en $1",
"editsection": "editar",
"editold": "editar",
"viewsourceold": "ver código",
"databaseerror-query": "Consulta: $1",
"databaseerror-function": "Función: $1",
"databaseerror-error": "Error: $1",
+ "transaction-duration-limit-exceeded": "Con el fin de evitar un aumento excesivo del retardo de replicación, se anuló esta transacción porque la duración de escritura ($1) excedió el límite de $2 {{PLURAL:$2|segundo|segundos}}.\nSi está cambiando muchos elementos a la vez, trate de hacer operaciones similares más pequeñas.",
"laggedslavemode": "<strong>Aviso:</strong> la página puede no contener las actualizaciones más recientes.",
+ "readonly": "Base de datos bloqueada",
"enterlockreason": "Proporcione el motivo del bloqueo, así como una estimación de cuándo se producirá el desbloqueo",
- "readonlytext": "La base de datos se encuentra actualmente bloqueada y no permite la creación de páginas nuevas y otras modificaciones, probablemente de forma temporal debido al mantenimiento rutinario de la base de datos. Después de esas operaciones el sitio se encontrará nuevamente disponible.\n\nLa razón dada por el administrador que bloqueó la base de datos es la que sigue: $1",
+ "readonlytext": "Actualmente la base de datos no permite nuevas entradas u otras modificaciones, probablemente por mantenimiento rutinario, tras lo cual volverá a la normalidad.\n\nLa explicación dada por el administrador que la bloqueó fue: $1",
"missing-article": "La base de datos no encuentra el texto de una página que debería hallarse, llamada \"$1\" $2.\n\nLa causa de esto suele deberse a un ''diff'' anacrónico o un enlace al historial de una página que ha sido borrada.\n\nSi no fuera el caso, usted puede haber encontrado un fallo en el sistema.\n\nPor favor, avise a un [[Special:ListUsers/sysop|administrador]], tomando nota de la URL.",
"internalerror": "Error interno",
"internalerror_info": "Error interno: $1",
"viewsource": "Ver código",
- "actionthrottledtext": "Como medida de protección contra el ''spam'', la acción que está realizando está limitada a un número determinado de veces en un periodo corto de tiempo. Usted ha excedido ese límite. Por favor pruebe de nuevo en unos minutos.",
- "viewsourcetext": "Puede ver y copiar el código fuente de esta página:",
- "editinginterface": "'''Aviso:''' Está usted editando una página usada para proporcionar texto de interfaz para el software. Los cambios en esta página afectarán a la apariencia de la interfaz para los demás usuarios. Para traducciones, por favor considere usar [//translatewiki.net/wiki/Main_Page?setlang=en translatewiki.net], el proyecto de traducción de MediaWiki.",
+ "actionthrottledtext": "Como medida contra los abusos, la acción que está realizando está limitada a un número determinado de veces en un periodo corto de tiempo, y ha excedido ese límite.\nPor favor inténtelo de nuevo en unos minutos.",
+ "viewsourcetext": "Puede ver y copiar el código fuente de esta página.",
+ "viewyourtext": "Puede ver y copiar el código de <strong>sus ediciones</strong> en esta página.",
+ "protectedinterface": "Esta página proporciona el texto de la interfaz del software en este wiki, y está protegida para prevenir el abuso.\nPara agregar o cambiar las traducciones para todos los wikis, use [https://translatewiki.net/ translatewiki.net], el proyecto de localización de MediaWiki.",
+ "editinginterface": "<strong>Advertencia:</strong> está editando una página usada para proporcionar texto de la interfaz al software. \nLos cambios en esta página afectarán la apariencia de la interfaz de los demás usuarios de este wiki.",
+ "translateinterface": "Para añadir o cambiar traducciones para todos los wikis, use [https://translatewiki.net/ translatewiki.net], el proyecto de traducción de MediaWiki.",
+ "namespaceprotected": "No tiene permiso para editar las páginas del espacio de nombres <strong>$1</strong>.",
+ "customcssprotected": "No tiene permiso para editar esta página CSS, porque contiene configuraciones personales de otro usuario.",
+ "customjsprotected": "No tiene permiso para editar esta página JavaScript, porque contiene configuraciones personales de otro usuario.",
+ "mycustomcssprotected": "No tiene permiso para editar esta página CSS.",
+ "mycustomjsprotected": "No tiene permiso para editar esta página JavaScript.",
+ "myprivateinfoprotected": "No tiene permiso para editar su información privada.",
+ "mypreferencesprotected": "No tiene permiso para editar sus preferencias.",
+ "exception-nologin-text": "Por favor inicie sesión para acceder a esta página o llevar a cabo esta acción.",
+ "exception-nologin-text-manual": "Necesita $1 para poder ver esta página o llevar a cabo esta acción.",
+ "logouttext": "<strong>Su sesión ha finalizado.</strong>\n\nPuede que algunas páginas continúen mostrándose como si la sesión estuviera iniciada hasta que actualice la caché de su navegador.",
"welcomeuser": "Le damos la bienvenida, $1.",
"welcomecreation-msg": "Se ha creado su cuenta.\nPuede cambiar las [[Special:Preferences|preferencias]] de {{SITENAME}} si lo desea.",
"yourname": "Nombre de usuario:",
"yourpasswordagain": "Escriba la contraseña otra vez:",
"createacct-yourpasswordagain": "Confirme la contraseña",
"createacct-yourpasswordagain-ph": "Escriba la contraseña otra vez",
+ "yourdomainname": "Su dominio:",
+ "password-change-forbidden": "No puede cambiar las contraseñas en este wiki.",
+ "externaldberror": "Hubo un error de autenticación en la base de datos, o bien no tiene autorización para actualizar su cuenta externa.",
"login": "Acceder",
+ "login-security": "Verifique su identidad",
"nav-login-createaccount": "Iniciar sesión / crear cuenta",
"logout": "Desconectar",
"userlogout": "Salir",
+ "notloggedin": "No ha accedido",
"userlogin-noaccount": "¿No tiene una cuenta?",
"userlogin-joinproject": "Únase a {{SITENAME}}",
"userlogin-resetpassword-link": "¿Olvidó su contraseña?",
+ "userlogin-loggedin": "Ya está {{GENDER:$1|conectado|conectada}} como $1.\nUse el formulario de abajo para iniciar sesión como otro usuario.",
+ "userlogin-reauth": "Debe iniciar sesión de nuevo para verificar que usted es {{GENDER:$1|$1}}.",
"createacct-emailrequired": "Dirección de correo electrónico",
"createacct-emailoptional": "Dirección de correo electrónico (opcional)",
- "anoneditwarning": "'''Aviso:''' No ha iniciado sesión con una cuenta de usuario.\nSu dirección IP se almacenará en el historial de ediciones de la página.",
+ "createacct-email-ph": "Escriba su dirección de correo electrónico",
+ "createacct-another-email-ph": "Escriba la dirección de correo electrónico",
+ "createacct-reason-ph": "Por qué está creando otra cuenta",
+ "createacct-submit": "Cree su cuenta",
+ "createacct-benefit-heading": "Personas como usted son las que construyen {{SITENAME}}.",
+ "badretype": "Las contraseñas que usted ha introducido no coinciden.",
+ "usernameinprogress": "Ya está en marcha la creación de una cuenta para este nombre de usuario.\nPor favor, espere.",
+ "userexists": "El nombre de usuario indicado ya está en uso.\nPor favor elija un nombre diferente.",
+ "nocookiesnew": "Se ha creado la cuenta de usuario, pero aún no ha iniciado sesión.\n{{SITENAME}} usa <em>cookies</em> para identificar a los usuarios registrados.\nSu navegador tiene desactivadas las <em>cookies</em>.\nPor favor, actívelas e inicie sesión con su nuevo nombre de usuario y contraseña.",
+ "nocookieslogin": "{{SITENAME}} utiliza <em>cookies</em> para la autenticación de usuarios. Las <em>cookies</em> están desactivadas en su navegador. Por favor, actívelas e inténtelo de nuevo.",
+ "nocookiesfornew": "No se pudo crear la cuenta de usuario, porque no pudimos confirmar su origen.\nAsegúrese de que tiene las <em>cookies</em> activadas, luego recargue esta página e inténtelo de nuevo.",
+ "createacct-loginerror": "La cuenta se ha creado correctamente, pero no se pudo ingresar automáticamente. Proceda al [[Special:UserLogin|acceso manual]].",
+ "loginsuccess": "<strong>Ha accedido a {{SITENAME}} como «$1».</strong>",
+ "nosuchuser": "No existe ninguna cuenta llamada «$1».\nLos nombres de usuario distinguen mayúsculas y minúsculas.\nCompruebe su escritura o [[Special:CreateAccount|cree una cuenta nueva]].",
+ "nosuchusershort": "No existe ningún usuario llamado «$1».\nCompruebe que lo ha escrito correctamente.",
+ "nouserspecified": "Debe especificar un nombre de usuario.",
+ "login-userblocked": "No puede iniciar sesión porque su cuenta está bloqueada.",
+ "wrongpassword": "El nombre de usuario o la contraseña que ha proporcionado son incorrectos.\nPor favor inténtelo de nuevo.",
+ "wrongpasswordempty": "No ha introducido una contraseña.\nPor favor inténtelo de nuevo.",
+ "password-name-match": "Su contraseña debe ser diferente de su nombre de usuario.",
+ "passwordsent": "Se ha enviado una nueva contraseña al correo electrónico de «$1».\nPor favor, identifíquese de nuevo tras recibirla.",
+ "anoneditwarning": "<strong>Advertencia:</strong> no ha iniciado sesión. Su dirección IP se hará pública si hace cualquier edición en estas condiciones. Si <strong>[$1 inicia sesión]</strong> o <strong>[$2 crea una cuenta]</strong>, sus ediciones se atribuirán a su nombre de usuario, además de otros beneficios.",
"newarticletext": "Ha seguido usted un enlace a una página que aún no existe.\nPara crear esta página, escriba en el campo a continuación. Para más información, consulte la [$1 página de ayuda].\nSi ha llegado aquí por error, vuelva a la página anterior.",
- "noarticletext": "En este momento no hay texto en esta página.\nPuede [[Special:Search/{{PAGENAME}}|buscar el título de esta página]] en otras páginas,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} buscar en los registros],\no [{{fullurl:{{FULLPAGENAME}}|action=edit}} editar esta página]</span>.",
+ "noarticletext": "En este momento no hay texto en esta página.\nPuede [[Special:Search/{{PAGENAME}}|buscar el título de esta página]] en otras páginas,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} buscar en los registros relacionados],\no [{{fullurl:{{FULLPAGENAME}}|action=edit}} crear esta página]</span>.",
"copyrightwarning": "Por favor observe que todas las contribuciones realizadas en {{SITENAME}} serán consideradas como liberadas bajo $2 (véase $1 para más detalles).\nSi no desea que sus escritos sean editados o redistribuídos a voluntad, entonces no contribuya aquí.<br />\nAl mismo tiempo está usted prometiendo que lo que usted va a enviar lo ha escrito usted, o copiado de una fuente de dominio público.\n'''¡No envíe textos con derechos de autor sin el debido permiso!'''",
"permissionserrorstext-withaction": "No tiene permiso para $2 por {{PLURAL:$1|la|las}} {{PLURAL:$1|siguiente|siguientes}} {{PLURAL:$1|razón|razones}}:",
"rev-deleted-text-permission": "Esta revisión de la página ha sido '''borrada'''.\nPuede encontrar detalles en el [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro de borrados].",
- "rev-deleted-text-unhide": "Esta revisión de página ha sido '''borrada'''.\nPuede haber detalles en el [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro de borrados].\nComo administrador todavía puede [$1 ver esta revisión] si así lo desea.",
- "rev-suppressed-text-unhide": "Esta revisión de la página ha sido '''suprimida'''.\nPuede haber detalles en el [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} registro de supresiones].\nComo administrador podrá seguir [$1 viendo esta revisión] si desea continuar.",
- "rev-deleted-text-view": "Esta revisión de la página ha sido '''borrada'''.\nComo administrador puede verla; puede haber detalles en el [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro de borrados].",
- "rev-suppressed-text-view": "Esta revisión de la página ha sido '''suprimida'''.\nComo administrador puede verla; puede haber detalles en el [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} registro de supresiones].",
+ "rev-deleted-text-unhide": "Esta revisión ha sido <strong>eliminada</strong>.\nPara más información, consulte el [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro de borrados].\nComo administrador, aún puede [$1 ver esta revisión] si lo desea.",
+ "rev-suppressed-text-unhide": "Esta revisión ha sido <strong>suprimida</strong>.\nPara más información, consulte el [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} registro de supresiones].\nComo supresor, aún puede [$1 ver esta revisión] si lo desea.",
+ "rev-deleted-text-view": "Esta revisión ha sido <strong>eliminada</strong>.\nAún tiene la posibilidad de verla. Para más información, consulte el [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro de borrados].",
+ "rev-suppressed-text-view": "Esta revisión ha sido <strong>suprimida</strong>.\nAún tiene la posibilidad de verla. Para más información, consulte el [{{fullurl:{{#Special:Log}}/suppress|page={{FULLPAGENAMEE}}}} registro de supresiones].",
"rev-deleted-no-diff": "No puede visualizarse este cambio debido a que las revisiones han sido '''borradas'''.\nPuede haber detalles en el [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro de borrados].",
- "revdelete-nooldid-text": "No se ha especificado una revisión o revisiones destino sobre las que realizar esta función.",
+ "revdelete-nooldid-text": "O bien no se ha especificado una revisión destino sobre la que realizar esta función, o bien la revisión especificada no existe, o bien está intentando ocultar la revisión actual.",
"revdelete-show-file-confirm": "¿Está seguro de que desea ver la revisión borrada del archivo \"<nowiki>$1</nowiki>\" del $2 a las $3?",
"revdelete-confirm": "Confirme que quiere realizar la operación, que entiende las consecuencias y que está ejecutando dicha acción acorde con [[{{MediaWiki:Policy-url}}|las políticas]].",
"lineno": "Línea $1:",
"rightslog": "Registro de cambios de permisos de usuarios",
"recentchanges": "Cambios recientes",
"recentchangeslinked-toolbox": "Cambios relacionados",
- "recentchangeslinked-summary": "Esta es una lista de cambios efectuados recientemente a páginas enlazadas desde una página dada (o de miembros de una categoría dada).\nLas páginas que se encuentran en tu [[Special:Watchlist|lista de seguimiento]] están en <strong>negritas</strong>.",
+ "recentchangeslinked-summary": "Escriba el nombre de una página para ver cambios realizados en páginas con enlaces entrantes o salientes a esa página. (Para ver lo que pertenece a una categoría, escriba «Categoría:Nombre de la categoría»). Los cambios en páginas de su [[Special:Watchlist|lista de seguimiento]] aparecen en <strong>negrita</strong>.",
"upload": "Subir archivo",
"filehist-help": "Haga clic sobre una fecha/hora para ver el archivo a esa fecha.",
"randompage": "Página aleatoria",
"nbytes": "$1 {{PLURAL:$1|byte|bytes}}",
"emailuser": "Enviar un correo electrónico a este usuario",
- "addedwatchtext": "La página «[[:$1]]» ha sido añadida a su [[Special:Watchlist|lista de seguimiento]]. Los cambios futuros en esta página y en su página de discusión asociada se indicarán ahí, y la página aparecerá '''en negritas''' en la [[Special:RecentChanges|lista de cambios recientes]] para hacerla más fácil de detectar.\n\nCuando quiera eliminar la página de su lista de seguimiento, presione «Dejar de vigilar» en el menú.",
+ "addedwatchtext": "Se han añadido «[[:$1]]» y su página de discusión a su [[Special:Watchlist|lista de seguimiento]].",
"removedwatchtext": "Se han eliminado «[[:$1]]» y su página de discusión de tu [[Special:Watchlist|lista de seguimiento]].",
"confirmdeletetext": "Está a punto de borrar una página junto con su historial.\nPor favor confirme que desea realizar esto, que entiende las consecuencias y que está realizando esta acción de acuerdo con las [[{{MediaWiki:Policy-url}}|políticas]]",
"deletedtext": "\"$1\" ha sido borrado.\nVea $2 para un registro de los borrados recientes.",
"rollbacklink": "revertir",
"protect-text": "Puede ver y modificar el nivel de protección de la página '''$1'''.",
"protect-locked-access": "Su cuenta no tiene permiso para cambiar los niveles de protección de una página.\nA continuación se muestran las opciones actuales de la página '''$1''':",
- "protect-cascadeon": "Actualmente esta página está protegida porque está incluida en {{PLURAL:$1|la siguiente página|las siguientes páginas}}, que tienen activada la opción de protección en cascada. Puede cambiar el nivel de protección de esta página, pero no afectará a la protección en cascada.",
+ "protect-cascadeon": "Actualmente esta página está protegida porque está transcluida en {{PLURAL:$1|la siguiente página, que tiene|las siguientes páginas, que tienen}} activada la opción de protección de cascada.\nPuede cambiar el nivel de protección de esta página, pero no afectará a la protección de cascada.",
"protect-cantedit": "No puede cambiar el nivel de protección porque no tiene permiso para editarla.",
"blanknamespace": "(Principal)",
"whatlinkshere": "Lo que enlaza aquí",
"confirmemail_body": "Alguien, probablemente usted mismo, ha registrado desde la dirección IP $1 la cuenta \"$2\" en {{SITENAME}}, utilizando esta dirección de correo.\n\nPara confirmar que esta cuenta realmente le pertenece y activar el correo en {{SITENAME}}, siga este enlace:\n\n$3\n\nSi la cuenta *no* es suya, siga este otro enlace para cancelar la confirmación de la dirección de correo:\n\n$5\n\nEl código de confirmación expirará en $4.",
"confirmemail_body_changed": "Alguien, probablemente usted,\nha modificado la dirección de correo electrónico asociado a la cuenta \"$2\" hacia esta en {{SITENAME}}, desde la dirección IP $1.\n\nPara confirmar que esta cuenta realmente le pertenece y reactivar las funciones de correo electrónico en {{SITENAME}}, abra este enlace en su navegador:\n\n$3\n\nSi la cuenta *no* le pertenece, sigua el siguiente enlace para cancelar la confirmación:\n\n$5\n\nEste código de confirmación expirará el $4.",
"deletedwhileediting": "'''Aviso''': ¡Esta página fue borrada después de que usted empezara a editar!",
- "confirmrecreate": "El usuario [[User:$1|$1]] ([[User talk:$1|discusión]]) borró este artículo después de que usted empezara a editarlo y dio esta razón:\n: ''$2'' \nPor favor, confirme que realmente desea crear de nuevo esta página.",
+ "confirmrecreate": "{{GENDER:$1|El usuario|La usuaria}} [[User:$1|$1]] ([[User talk:$1|discusión]]) borró esta página después de que usted comenzara a editarla, por este motivo:\n: <em>$2</em>\nPor favor confirme que realmente quiere volver a crearla.",
"watchlistedit-normal-explain": "Los títulos de su lista de seguimiento se muestran debajo.\nPara eliminar un título, marque la casilla junto a él, y haga clic en ''{{int:Watchlistedit-normal-submit}}''.\nTambién puede [[Special:EditWatchlist/raw|editar la lista de en crudo]].",
"watchlistedit-raw-explain": "Los títulos de su lista de seguimiento se muestran debajo. Esta lista puede ser editada añadiendo o eliminando líneas de la lista;\nun título por línea.\nCuando acabe, haga clic en \"{{int:Watchlistedit-raw-submit}}\".\nTambién puede [[Special:EditWatchlist|usar el editor estándar]].",
"watchlistedit-raw-done": "Su lista de seguimiento ha sido actualizada.",
"delete_and_move_text": "Kohdesivu [[:$1]] on jo olemassa. \nHaluatko poistaa sen, jotta nykyinen sivu voitaisiin siirtää sen tilalle?",
"delete_and_move_confirm": "Kyllä, poista kohdesivu",
"delete_and_move_reason": "Sivu on sivun [[$1]] siirron tiellä.",
- "selfmove": " Nimi on sama;\nSivua ei voi siirtää itsensä päälle.",
+ "selfmove": "Nimi on sama;\nSivua ei voi siirtää itsensä päälle.",
"immobile-source-namespace": "Sivuja ei voi siirtää nimiavaruudessa ”$1”",
"immobile-target-namespace": "Sivuja ei voi siirtää nimiavaruuteen ”$1”",
"immobile-target-namespace-iw": "Kielilinkki ei ole kelvollinen kohde sivun siirrolle.",
"protectedpagetext": "Cette page a été protégée pour empêcher sa modification ou d’autres actions.",
"viewsourcetext": "Vous pouvez voir et copier le contenu de cette page.",
"viewyourtext": "Vous pouvez voir et copier le contenu de <strong>vos modifications</strong> à cette page.",
- "protectedinterface": "Cette page fournit du texte d’interface pour le logiciel sur ce wiki et est protégée pour éviter les abus.\nPour ajouter ou modifier des traductions sur tous les wikis, veuillez utiliser [https://translatewiki.net/ translatewiki.net], le projet de localisation de MediaWiki.",
+ "protectedinterface": "Cette page fournit du texte d’interface pour le logiciel sur ce wiki et est protégée pour éviter les abus.\nPour ajouter ou modifier des traductions sur tous les wikis, veuillez utiliser [https://translatewiki.net/ translatewiki.net], le projet de régionalisation de MediaWiki.",
"editinginterface": "<strong>Attention :</strong> vous êtes en train de modifier une page utilisée pour créer le texte de l’interface du logiciel.\nLes changements sur cette page se répercuteront sur l’apparence de l’interface utilisateur pour les autres utilisateurs de ce wiki.",
"translateinterface": "Pour ajouter ou modifier des traductions pour tous les wikis, veuillez utiliser [https://translatewiki.net/ translatewiki.net], le projet de localisation linguistique de MediaWiki.",
"cascadeprotected": "Cette page est protégée contre les modifications car elle est transcluse par {{PLURAL:$1|la page suivante, qui a été protégée|les pages suivantes, qui ont été protégées}} avec l’option « protection en cascade » activée :\n$2",
"Duolaimi",
"Impersonator 1",
"Babanwalia",
- "Macofe"
+ "Macofe",
+ "Fanjiayi"
]
},
"tog-underline": "下划链接",
"nstab-template": "模版",
"nstab-help": "帮助页",
"nstab-category": "分类",
+ "mainpage-nstab": "封面",
"nosuchaction": "冇有个只命令",
"nosuchactiontext": "Wiki识别伓到个只URL命令",
"nosuchspecialpage": "冇有个只特殊页",
"createaccount-title": "到{{SITENAME}}创建𠮶帐户",
"createaccount-text": "有人到{{SITENAME}}用倷𠮶电子邮件地址开设喽只名字系 \"$2\" 𠮶新帐户($4),密码系 \"$3\" 。请倷仰上登录同到修改密码。\n\n要系帐户创建不对𠮶话,倷就莫搭个只消息。",
"loginlanguagelabel": "语言: $1",
+ "pt-login": "登入",
+ "pt-createaccount": "新开只帐户",
"changepassword": "改过密码",
"resetpass_announce": "倷系用到临时email𠮶代码登入𠮶。要登正入,倷要到个首设定只新密码:",
"resetpass_header": "设过密码",
"rclistfrom": "显示自$3 $2后𠮶新改动",
"rcshowhideminor": "$1细编辑",
"rcshowhidebots": "$1机器人𠮶编辑",
- "rcshowhideliu": "$1登入用户𠮶编辑",
+ "rcshowhideliu": "$1注册用户",
"rcshowhideanons": "$1匿名用户𠮶编辑",
"rcshowhidepatr": "$1检查过𠮶编辑",
"rcshowhidemine": "$1偶𠮶编辑",
- "rclinks": "显示最晏$2日之内最新𠮶$1回改动。",
+ "rclinks": "显示最晏$2日之内最新𠮶$1个改动。",
"diff": "差异",
"hist": "历史",
"hide": "弆到",
"namespace": "空间名:",
"invert": "反选",
"blanknamespace": "(主要)",
- "contributions": "用户贡献",
+ "contributions": "{{GENDER:$1|用户}}贡献",
"contributions-title": "$1𠮶用户贡献",
"mycontris": "偶𠮶贡献",
"contribsub2": "$1𠮶贡献 ($2)",
"tooltip-t-recentchangeslinked": "从个页连出𠮶全部页面𠮶改动",
"tooltip-feed-rss": "个页𠮶RSS订阅",
"tooltip-feed-atom": "个页𠮶Atom订阅",
- "tooltip-t-contributions": "望吖个只用户𠮶贡献",
+ "tooltip-t-contributions": "由{{GENDER:$1|此用户}}做出的贡献列表",
"tooltip-t-emailuser": "发封邮件到个只用户",
"tooltip-t-upload": "上传图像或多媒体文件",
"tooltip-t-specialpages": "全部特殊页列表",
"file-info-size": "$1 × $2 像素,档案大细:$3 ,MIME类型:$4",
"file-nohires": "冇更高分辨率𠮶图像。",
"svg-long-desc": "SVG档案,表面大细: $1 × $2 像素,档案大细:$3",
- "show-big-image": "å®\8cæ\95´å\88\86辨ç\8e\87",
+ "show-big-image": "å\8e\9få§\8bæ\96\87件",
"newimages": "新建图像画廊",
"imagelisttext": "底下系按$2排列𠮶$1只档案列表。",
"noimages": "冇什哩可望。",
"external_image_whitelist": "#留住个行字<pre>\n#到下首(//𠮶中间)输入正规表达式\n#佢俚会同得外部(已超连结𠮶)图片配合\n#许滴配合到出来𠮶会显示做图片,否则就光会显示做连结\n#有 # 开头𠮶行会当做注解\n#大小写冇有差别\n\n#到个行上首输入所有𠮶regex。留住个行字</pre>",
"tag-filter": "[[Special:Tags|标签]]过滤器:",
"rightsnone": "(冇)",
- "searchsuggest-search": "寻吖"
+ "searchsuggest-search": "寻吖{{SITENAME}}"
}
"아라",
"Srdjan m",
"Macofe",
- "Stavanger7"
+ "Stavanger7",
+ "Fanjiayi"
]
},
"tog-underline": "Ultracatenun:",
"october-date": "$1 octobre",
"november-date": "$1 novembre",
"december-date": "$1 decembre",
+ "period-am": "AM",
+ "period-pm": "PM",
"pagecategories": "{{PLURAL:$1|Categorie|Categories}}",
"category_header": "Articules in categorie \"$1\"",
"subcategories": "Subcategories",
"newwindow": "(es apertet in un nov fenestre)",
"cancel": "Anullar",
"moredotdotdot": "Plu...",
- "morenotlisted": "Ti liste ne es complet.",
+ "morenotlisted": "Forsan ti liste es íncomplet.",
"mypage": "Págine",
"mytalk": "Conversation",
"anontalk": "Discussion",
"badaccess-groups": "Ti action es limitat a usatores in {{PLURAL:$2|li gruppe|un del secuent gruppes:}} $1",
"versionrequired": "Version $1 de MediaWiki exiget",
"versionrequiredtext": "Version $1 de MediaWiki es exiget por usar ti págine.\nVider [[Special:Version|págine de version]].",
+ "ok": "OK",
"retrievedfrom": "Cargat de «$1»",
"youhavenewmessages": "Vu have $1 ($2).",
"youhavenewmessagesfromusers": "Tu have $1 de {{PLURAL:$3|un altri usator|$3 usatores}} ($2).",
"nstab-template": "Avise",
"nstab-help": "Auxilie",
"nstab-category": "Categorie",
+ "mainpage-nstab": "Principal págine",
"nosuchaction": "Null tal action existe",
"nosuchactiontext": "Li action indicat in li URL es ínvalid.\nForsan tu ha mistypat li URL o secuet un íncorrect ligament.\nForsan it indica un erra in li programma usat de {{SITENAME}}.",
"nosuchspecialpage": "Null tal special págine",
"createaccount-title": "Creation de conto por {{SITENAME}}",
"loginlanguagelabel": "Lingue: $1",
"suspicious-userlogout": "Tui petition por surtir esset desaprobat pro que probabilmen esset inviat per un navigator ruptet o servitor de autorisation che caching.",
+ "pt-login": "Aperter session",
"pt-login-button": "Aperter session",
"pt-createaccount": "Crear un conto",
+ "pt-userlogout": "Surtir",
"changepassword": "Modificar passa-parol",
"oldpassword": "Anteyan passa-parol:",
"newpassword": "Nov passa-parol:",
"template-protected": "(protectet)",
"template-semiprotected": "(medie-gardat)",
"hiddencategories": "Ti págine es un membre de {{PLURAL:$1|1 categorie ocultat|$1 categories ocultat}}:",
+ "permissionserrors": "Tu ne have sufficent jures",
"permissionserrorstext-withaction": "Vu ne have permission por $2, por li sequent {{PLURAL:$1|motive|motives}}:",
"recreate-moveddeleted-warn": "'''Advertiment: Vu es recreant un págine que esset anteriorimen deletet.'''\n\nVu deve considerar ca it es convenent por continuar redactant ti págine.\nLi deletion e diarium de movement por li págine es sub li condition ci por convenience:",
"moveddeleted-notice": "Ti págine ha esset deletet.\nLi deletion e diarium de movement por li págine es sub li condition in infra por referentie.",
"rclistfrom": "Monstrar li nov modificationes desde $3 $2",
"rcshowhideminor": "$1 redactiones minori",
"rcshowhideminor-show": "Monstrar",
+ "rcshowhideminor-hide": "Ocultar",
"rcshowhidebots": "$1 machines",
+ "rcshowhidebots-show": "Monstrar",
"rcshowhidebots-hide": "Ocultar",
"rcshowhideliu": "$1 usatores registrat",
+ "rcshowhideliu-show": "Monstrar",
"rcshowhideliu-hide": "Ocultar",
"rcshowhideanons": "$1 usatores anonim",
"rcshowhideanons-show": "Monstrar",
+ "rcshowhideanons-hide": "Ocultar",
"rcshowhidepatr": "$1 redactiones vigilat",
"rcshowhidepatr-hide": "Ocultar",
"rcshowhidemine": "$1 mi redactiones",
"rcshowhidemine-show": "Monstrar",
+ "rcshowhidemine-hide": "Ocultar",
"rclinks": "Monstrar li $1 ultim modificationes fat durante li $2 ultim dies",
"diff": "dif",
"hist": "hist",
"tooltip-pt-login": "Tu es incorrageat crear un conto, ma to ne es un deventie.",
"tooltip-pt-logout": "Surtir",
"tooltip-ca-talk": "Discussion pri li articul.",
- "tooltip-ca-edit": "Redacter ti págine. Ples usar li buton de prevision antequam conservar.",
+ "tooltip-ca-edit": "Redacter ti-ci págine",
"tooltip-ca-addsection": "Comensar un nov section",
"tooltip-ca-viewsource": "Ti págine es protectet. Ma tu posse vider e copiar su fonte.",
"tooltip-ca-history": "Passat versiones de ti págine",
"tooltip-preferences-save": "Conservar preferenties",
"tooltip-summary": "Ples intrar un curt resummation.",
"simpleantispam-label": "Control anti-spam.\n<strong>Ne</strong> plena to ci!",
+ "pageinfo-article-id": "Págine ID",
"pageinfo-toolboxlink": "Information pri li págine",
"previousdiff": "← Redaction anteriori",
"nextdiff": "Proxim redaction →",
"version-software": "Software installat",
"version-software-product": "Producte",
"version-software-version": "Version",
+ "redirect-submit": "Ear",
+ "redirect-user": "Usator ID",
"specialpages": "Special págines",
"specialpages-group-maintenance": "Raportes de conservation",
"specialpages-group-other": "Altri págines special",
"tags-display-header": "Aspecte in listes de change",
"tags-description-header": "Descrition complet de signification",
"tags-hitcount-header": "Changes nómiat",
+ "tags-active-yes": "Yes",
"tags-edit": "redacter",
"tags-hitcount": "$1 {{PLURAL:$1|change|changes}}",
"logentry-delete-delete": "$1 ha removet li págine $3",
"backend-fail-read": "Negalima nuskaityti failo $1.",
"backend-fail-create": "Negalima sukurti failo $1.",
"backend-fail-maxsize": "Failo $1 sukurti nepavyko nes jis didesnis nei {{PLURAL:$2|vienas baitas|$2 baitai|$2 baitų}}.",
- "backend-fail-readonly": "Galutinė saugykla \"$1\" dabar yra skirta tik skaitymui. Buvo nurodyta priežastis: \"$2\"",
+ "backend-fail-readonly": "Galutinė saugykla \"$1\" dabar yra skirta tik skaitymui. Nurodyta priežastis: <em>$2</em>",
"backend-fail-synced": "Failas \"$1\", esantis vidinėje galutinėje saugykloje, yra pažymėtas kaip nepilnas.",
"backend-fail-connect": "Negalima prisijungti prie galutinės saugyklos \"$1\".",
"backend-fail-internal": "Nežinoma klaida įvyko galutinėje saugykloje \"$1\".",
"editcomment": "Pateiktas toks keitimo paaiškinimas: <em>$1</em>.",
"revertpage": "Atmestas [[Special:Contributions/$2|$2]] ([[User talk:$2|Aptarimas]]) pakeitimas; sugrąžinta [[User:$1|$1]] versija",
"revertpage-nouser": "Atversti pakeitimai paslėpto vartotojo, grąžino prieš tai buvusią versiją {{GENDER:$1|[[User:$1|$1]]}}",
- "rollback-success": "Atmesti $1 pakeitimai;\ngrąžinta prieš tai buvusi $2 versija.",
+ "rollback-success": "Atmesti {{GENDER:$3|$1}} pakeitimai;\ngrąžinta prieš tai buvusi {{GENDER:$4|$2}} versija.",
"rollback-success-notify": "Atmesti $1 pakeitimai;\ngrąžinta prieš tai buvusi $2 versija. [$3 Rodyti skirtumus]",
"sessionfailure-title": "Sesijos klaida",
"sessionfailure": "Atrodo yra problemų su jūsų prisijungimo sesija; šis veiksmas buvo atšauktas kaip atsargumo priemonė prieš sesijos vogimą.\nPrašome paspausti „atgal“ ir perkraukite puslapį iš kurio atėjote, ir pamėginkite vėl.",
"import-nonewrevisions": "Nebuvo importuotos jokios versijos (visos jau buvo įkeltos arba praleistos dėl klaidų).",
"xml-error-string": "$1 $2 eilutėje, $3 stulpelyje ($4 baitas): $5",
"import-upload": "Įkelti XML duomenis",
- "import-token-mismatch": "Sesijos duomenys prarasti.\n\nGali būti, kad esate atsijungęs. <strong>Prašome patikrinti, ar vis dar esate prisijungęs, ir pabandyti iš naujo</strong>.\nJei ir toliau nepavyksta, pamėginkite [[Special:UserLogout|atsijungti]] ir vėl prisijungti, taip pat patikrinkite, ar jūsų naršyklė priima šios svetainės slapukus.",
+ "import-token-mismatch": "Sesijos duomenys prarasti.\n\nGali būti, kad esate atsijungęs. '''Prašome patikrinti, ar vis dar esate prisijungęs, ir pabandyti iš naujo'''.\nJei ir toliau nepavyksta, pamėginkite [[Special:UserLogout|atsijungti]] ir vėl prisijungti, taip pat patikrinkite, ar jūsų naršyklė priima šios svetainės slapukus.",
"import-invalid-interwiki": "Nepavyko importuoti iš nurodyto wiki projekto.",
"import-error-edit": "Puslapis \"$1\" nebuvo įkeltas, nes jūs neturite teisės jį redaguoti.",
"import-error-create": "Puslapis „$1“ nebuvo importuotas, nes jūs neturite teisės jį sukurti.",
"toc": "Isi kandungan",
"showtoc": "tunjukkan",
"hidetoc": "sorokkan",
- "collapsible-collapse": "Lipat",
- "collapsible-expand": "Kembangkan",
+ "collapsible-collapse": "Tutup",
+ "collapsible-expand": "Buka",
"confirmable-confirm": "Pastikah {{GENDER:$1|anda}}?",
"confirmable-yes": "Ya",
"confirmable-no": "Tidak",
"botpasswords-created-title": "Kata laluan bot telah dicipta",
"botpasswords-created-body": "Kata laluan bot untuk nama bot \"$1\" bagi {{GENDER:$2|pengguna}} \"$2\" telah dicipta.",
"botpasswords-updated-title": "Kata laluan bot telah dikemaskinikan",
- "botpasswords-updated-body": "Kata laluan bot untuk nama bot \"$1\" bagi {GENDER:$2|pengguna}} \"$2\" telah dikemaskini.",
+ "botpasswords-updated-body": "Kata laluan bot untuk nama bot \"$1\" bagi {{GENDER:$2|pengguna}} \"$2\" telah dikemaskini.",
"botpasswords-deleted-title": "Kata laluan bot telah dipadam",
"botpasswords-deleted-body": "Kata laluan bot untuk nama bot \"$1\" bagi {{GENDER:$2|pengguna}} \"$2\" telah dipadam.",
"botpasswords-newpassword": "Kata laluan baru untuk log masuk dengan <strong>$1</strong> adalah <strong>$2</strong>. <em>Sila catatkan ini untuk rujukan masa depan.</em> <br> (Untuk bot-bot lama yang memerlukan nama log masuk agar sama dengan nama pengguna akhirnya, anda juga boleh menggunakan <strong>$3</strong> sebagai nama pengguna dan <strong>$4</strong> sebagai kata laluan.)",
"newarticle": "(Baru)",
"newarticletext": "Anda telah mengikuti pautan ke laman yang belum wujud.\nUntuk mencipta laman ini, sila taip dalam kotak di bawah\n(lihat [$1 laman bantuan] untuk maklumat lanjut).\nJika anda tiba di sini secara tak sengaja, hanya klik butang '''back''' pada pelayar anda.",
"anontalkpagetext": "<em>Ini ialah laman perbincangan bagi pengguna tanpa nama yang belum membuka akaun atau tidak menggunakannya.</em>\nOleh itu, kami terpaksa menggunakan alamat IP angka untuk mengenal pasti pengguna tersebut. Alamat IP ini boleh dikongsi oleh ramai pengguna.\nSekiranya anda ialah seorang pengguna tanpa nama dan berasa bahawa komen yang tidak relevan telah ditujukan kepada anda, sila [[Special:CreateAccount|buka akaun baru]] atau [[Special:UserLogin|log masuk]] untuk mengelakkan sebarang kekeliruan dengan pengguna tanpa nama yang lain.",
- "noarticletext": "Laman ini buat masa sekarang tidak berteks. Anda boleh [[Special:Search/{{PAGENAME}}|cari tajuk bagi laman ini]] dalam laman-laman lain, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} cari log-log yang berkaitan], atau [{{fullurl:{{FULLPAGENAME}}|action=edit}} sunting laman ini]</span>.",
+ "noarticletext": "Laman ini tiada teks buat masa sekarang.\nAnda boleh [[Special:Search/{{PAGENAME}}|cari tajuk bagi laman ini]] di laman-laman lain, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} cari log-log yang berkaitan], atau [{{fullurl:{{FULLPAGENAME}}|action=edit}} sunting laman ini]</span>.",
"noarticletext-nopermission": "Tiada teks dalam laman ini ketika ini.\nAnda boleh [[Special:Search/{{PAGENAME}}|mencari tajuk laman ini]] dalam laman lain,\natau <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} mencari log yang berkaitan]</span>.",
"missing-revision": "Semakan #$1 pada halaman \"{{FULLPAGENAME}}\" tidak wujud.\n\nHal ini biasanya disebabkan oleh pautan sejarah yang lapuk ke halaman yang sudah dihapuskan.\nButirannya boleh didapati di [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} log penghapusan].",
"userpage-userdoesnotexist": "Akaun pengguna \"<nowiki>$1</nowiki>\" tidak berdaftar. Sila pastikan sama ada anda mahu mencipta/menyunting laman ini.",
"postedit-confirmation-created": "Halaman telah diwujudkan.",
"postedit-confirmation-restored": "Halaman telah dipulihkan.",
"postedit-confirmation-saved": "Suntingan anda telah disimpan.",
+ "postedit-confirmation-published": "Suntingan anda telah disiarkan.",
"edit-already-exists": "Tidak dapat mencipta laman baru kerana ia telah wujud.",
"defaultmessagetext": "Teks mesej asal",
"content-failed-to-parse": "Kandungan $2 tidak dapat dihuraikan untuk model $1: $3",
"invalid-content-data": "Data kandungan tidak sah",
"content-not-allowed-here": "Kandungan \"$1\" tidak dibenarkan di halaman [[$2]]",
"editwarning-warning": "Meninggalkan laman ini mungkin akan menyebabkan sebarang perubahan yang telah anda lakukan hilang.\nJika anda sudah log masuk, anda boleh melumpuhkan amaran ini di bahagian \"{{int:prefs-editing}}\" dalam keutamaan anda.",
+ "editpage-invalidcontentmodel-title": "Model kandungan tidak disokong",
+ "editpage-invalidcontentmodel-text": "Model kandungan \"$1\" tidak disokong.",
"editpage-notsupportedcontentformat-title": "Format kandungan tidak disokong",
"editpage-notsupportedcontentformat-text": "Format kandungan $1 tidak disokong oleh model kandungan $2.",
"content-model-wikitext": "wikiteks",
"mergehistory-empty": "Tiada semakan yang boleh digabungkan",
"mergehistory-done": "$3 semakan bagi $1 telah digabungkan ke dalam [[:$2]].",
"mergehistory-fail": "Gagal melaksanakan penggabungan sejarah, sila semak semula laman tersebut dan parameter waktu.",
+ "mergehistory-fail-bad-timestamp": "Cap masa tidak sah.",
+ "mergehistory-fail-invalid-source": "Halaman asal tidak sah.",
+ "mergehistory-fail-invalid-dest": "Halaman tujuan tidak sah.",
"mergehistory-fail-toobig": "Tidak dapat melakukan gabungan sejarah sebab lebih daripada had $1 semakan perlu dipindahkan.",
"mergehistory-no-source": "Laman sumber $1 tidak wujud.",
"mergehistory-no-destination": "Laman destinasi $1 tidak wujud.",
"searchprofile-advanced-tooltip": "Cari dalam ruang nama yang tersuai",
"search-result-size": "$1 ({{PLURAL:$2|$2 patah perkataan}})",
"search-result-category-size": "$1 {{PLURAL:$1|ahli|ahli}} ($2 {{PLURAL:$2|subkategori|subkategori}}, $3 {{PLURAL:$3|fail|fail}})",
- "search-redirect": "(pelencongan $1)",
+ "search-redirect": "(lencongan dari $1)",
"search-section": "(bahagian $1)",
"search-category": "(kategori $1)",
"search-file-match": "(sepadan dengan kandungan fail)",
"search-interwiki-caption": "Hasil dari projek lain",
"search-interwiki-default": "Hasil dari $1:",
"search-interwiki-more": "(lagi)",
+ "search-interwiki-more-results": "hasil-hasil selanjutnya",
"search-relatedarticle": "Berkaitan",
"searchrelated": "berkaitan",
"searchall": "semua",
"username": "{{GENDER:$1|Nama pengguna}}:",
"prefs-memberingroups": "{{GENDER:$2|Ahli}} {{PLURAL:$1|kumpulan|kumpulan-kumpulan}}:",
"prefs-memberingroups-type": "$1",
+ "group-membership-link-with-expiry": "$1 (hingga $2)",
"prefs-registration": "Waktu pendaftaran:",
"prefs-registration-date-time": "$1",
"yourrealname": "Nama sebenar:",
"grant-editmywatchlist": "Sunting senarai pantau anda",
"grant-editpage": "Sunting laman sedia ada",
"grant-editprotected": "Sunting laman yang dilindungi",
+ "grant-uploadfile": "Muat naik fail baru",
+ "grant-basic": "Hak-hak asas",
"newuserlogpage": "Log akaun baru",
"newuserlogpagetext": "Yang berikut ialah log penciptaan pengguna.",
"rightslog": "Log hak pengguna",
"rcfilters-other-review-tools": "Alat semakan lain",
"rcfilters-activefilters": "Penapis yang aktif",
"rcfilters-savedqueries-defaultlabel": "Penapis yang disimpan",
+ "rcfilters-savedqueries-setdefault": "Tetapkan sebagai asali",
+ "rcfilters-savedqueries-unsetdefault": "Gugurkan sebagai asali",
+ "rcfilters-savedqueries-remove": "Gugurkan",
+ "rcfilters-savedqueries-new-name-label": "Nama",
"rcfilters-savedqueries-add-new-title": "Simpan tetapan penapis terkini",
"rcfilters-filter-humans-label": "Manusia (bukan bot)",
"rcfilters-filter-pageedits-label": "Suntingan laman",
"unpatrolledletter": "{{optional}}\n\nUsed in {{msg-mw|Recentchanges-label-legend}}, meaning \"unpatrolled\".",
"number_of_watching_users_RCview": "{{notranslate}}\nParameters:\n* $1 - number of users who are watching",
"number_of_watching_users_pageview": "Used if <code>$wgPageShowWatchingUsers</code> is true.\n* $1 - number of watching user(s)",
- "rc_categories": "A label of an input box. Appears on Special:RecentChanges if filtering recent changes by category is enabled (that is, $wgAllowCategorizedRecentChanges is set to true).",
- "rc_categories_any": "Appears ''after'' the input box the label of which is {{msg-mw|rc_categories}}, which appears on [[Special:RecentChanges]], if <code>$wgAllowCategorizedRecentChanges</code> is true. \"Chosen\" refers to categories.",
"rc-change-size": "{{optional}}\nDoes not work under $wgMiserMode ([[mwr:48986|r48986]]).\n\nParameters:\n* $1 - size of diff",
"rc-change-size-new": "Tooltip when hovering a change list diff size. Parameters:\n* $1 - the resulting new size (in bytes)",
"newsectionsummary": "Default summary when adding a new section to a page. Parameters:\n* $1 - section title",
"rollback-success": "Откачены правки {{GENDER:$3|$1}}; возврат к версии {{GENDER:$4|$2}}.",
"rollback-success-notify": "Откачены правки $1; возврат к последней версии $2. [$3 Показать изменения]",
"sessionfailure-title": "Ошибка сеанса",
- "sessionfailure": "Ð\9fоÑ\85оже, возникли пÑ\80облемÑ\8b Ñ\81 Ñ\82екÑ\83Ñ\89им Ñ\81еанÑ\81ом Ñ\80абоÑ\82Ñ\8b;\nÑ\8dÑ\82о дейÑ\81Ñ\82вие бÑ\8bло оÑ\82менено в Ñ\86елÑ\8fÑ\85 пÑ\80едоÑ\82вÑ\80аÑ\89ениÑ\8f «заÑ\85ваÑ\82а Ñ\81еанÑ\81а».\nÐ\9fожалÑ\83йÑ\81Ñ\82а, нажмиÑ\82е кнопкÑ\83 «Ð\9dазад» и пеÑ\80езагÑ\80Ñ\83зиÑ\82е Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, Ñ\81 коÑ\82оÑ\80ой вÑ\8b пÑ\80иÑ\88ли.",
+ "sessionfailure": "Ð\9fоÑ\85оже, возникли пÑ\80облемÑ\8b Ñ\81 Ñ\82екÑ\83Ñ\89им Ñ\81еанÑ\81ом Ñ\80абоÑ\82Ñ\8b;\nÑ\8dÑ\82о дейÑ\81Ñ\82вие бÑ\8bло оÑ\82менено в Ñ\86елÑ\8fÑ\85 пÑ\80едоÑ\82вÑ\80аÑ\89ениÑ\8f «заÑ\85ваÑ\82а Ñ\81еанÑ\81а».\nÐ\9fожалÑ\83йÑ\81Ñ\82а, пеÑ\80еоÑ\82пÑ\80авÑ\8cÑ\82е Ñ\84оÑ\80мÑ\83.",
"changecontentmodel": "Редактирование контентной модели страницы",
"changecontentmodel-legend": "Изменить модель содержимого",
"changecontentmodel-title-label": "Заголовок страницы",
"feedback-subject": "Tema:",
"feedback-submit": "Unesi",
"feedback-thanks": "Hvala! Vaša povratna informacija je postavljena na stranicu „[$2 $1]“.",
- "searchsuggest-search": "Traži",
+ "searchsuggest-search": "Traži {{GRAMMAR:akuzativ|{{SITENAME}}}}",
"searchsuggest-containing": "sadrži...",
"api-error-badtoken": "Unutrašnja greška: token nije ispravan.",
"api-error-emptypage": "Stvaranje novih praznih stranica nije dozvoljeno.",
"Kosovastar",
"Matma Rex",
"Arianit",
- "Denisa"
+ "Denisa",
+ "Fanjiayi"
]
},
"tog-underline": "Nënvizimi i lidhjes:",
"thu": "Enj",
"fri": "Pre",
"sat": "Sht",
- "january": "Janar",
- "february": "Shkurt",
- "march": "Mars",
- "april": "Prill",
- "may_long": "Maj",
- "june": "Qershor",
- "july": "Korrik",
- "august": "Gusht",
- "september": "Shtator",
- "october": "Tetor",
- "november": "Nëntor",
- "december": "Dhjetor",
- "january-gen": "Janar",
- "february-gen": "Shkurt",
- "march-gen": "Mars",
- "april-gen": "Prill",
- "may-gen": "Maj",
- "june-gen": "Qershor",
- "july-gen": "Korrik",
- "august-gen": "Gusht",
- "september-gen": "Shtator",
- "october-gen": "Tetor",
- "november-gen": "Nëntor",
- "december-gen": "Dhjetor",
+ "january": "janar",
+ "february": "shkurt",
+ "march": "mars",
+ "april": "prill",
+ "may_long": "maj",
+ "june": "qershor",
+ "july": "korrik",
+ "august": "gusht",
+ "september": "shtator",
+ "october": "tetor",
+ "november": "nëntor",
+ "december": "dhjetor",
+ "january-gen": "janar",
+ "february-gen": "shkurt",
+ "march-gen": "mars",
+ "april-gen": "prill",
+ "may-gen": "maj",
+ "june-gen": "qershor",
+ "july-gen": "korrik",
+ "august-gen": "gusht",
+ "september-gen": "shtator",
+ "october-gen": "tetor",
+ "november-gen": "nëntor",
+ "december-gen": "dhjetor",
"jan": "Jan",
"feb": "Shk",
"mar": "Mar",
"permissionserrorstext": "Nuk keni leje për të bërë këtë veprim për {{PLURAL:$1|këtë arsye|këto arsye}}:",
"permissionserrorstext-withaction": "Ju nuk keni leje për $2, për {{PLURAL:$1|këtë arsye|këto arsye}}:",
"recreate-moveddeleted-warn": "'''Kujdes: Po rikrijoni një faqe që është grisur më parë.'''\n\nMendohuni nëse dëshironi të vazhdoni me veprimin tuaj në këtë faqe.\nRegjistri i grisjes për këtë faqe jepet më poshtë:",
- "moveddeleted-notice": "Kjo faqe është grisur. Të dhënat e grisjes për këtë faqe gjenden më poshtë, për referencë.",
+ "moveddeleted-notice": "Kjo faqe është grisur. Të dhënat, protection e grisjes për këtë faqe gjenden më poshtë, për referencë.",
"log-fulllog": "Shihe ditaret të plota",
"edit-hook-aborted": "Redaktimi u ndërpre nga një goditje.\nNuk dha asnjë shpjegim.",
"edit-gone-missing": "Faqja nuk mund t freskohet.\nDuket se është grisur.",
"recentchangeslinked-feed": "Ndryshime të ndërvarura",
"recentchangeslinked-toolbox": "Ndryshime të ndërvarura",
"recentchangeslinked-title": "Ndryshime që kanë lidhje me \"$1\"",
- "recentchangeslinked-summary": "Kjo është një listë e ndryshimeve së fundmi të faqeve të lidhura nga faqja e dhënë (ose bëjnë pjesë tek kategoria e dhënë).\nFaqet [[Special:Watchlist|nën mbikqyrjen tuaj]] duken të '''theksuara'''.",
+ "recentchangeslinked-summary": "Kjo është një listë e ndryshimeve së fundmi të faqeve të lidhura nga faqja e dhënë (ose bëjnë pjesë tek kategoria e dhënë).\nFaqet [[Special:Watchlist|nën mbikqyrjen tuaj]] duken të <strong>theksuara</strong>.",
"recentchangeslinked-page": "Emri i faqes:",
"recentchangeslinked-to": "Trego ndryshimet e faqeve që lidhen tek faqja e dhënë",
"recentchanges-page-added-to-category": "[[:$1]] shtuar në kategori",
"tog-minordefault": "Означавај све измене као мање",
"tog-previewontop": "Прикажи претпреглед пре оквира за уређивање",
"tog-previewonfirst": "Прикажи преглед на првој измени",
- "tog-enotifwatchlistpages": "Пошаљи ми имејл када се измени страница или датотека коју надгледам",
+ "tog-enotifwatchlistpages": "Пошаљи ми имејл када се страница или датотека коју надгледам измени",
"tog-enotifusertalkpages": "Пошаљи ми имејл када се моја страница за разговор измени",
"tog-enotifminoredits": "Пошаљи ми имејл и за мање измене страница и датотека",
"tog-enotifrevealaddr": "Прикажи моју имејл адресу у порукама обавештења",
"tog-shownumberswatching": "Прикажи број корисника који надгледају",
- "tog-oldsig": "Ð\92аÑ\88 Ñ\82ренутни потпис:",
+ "tog-oldsig": "Тренутни потпис:",
"tog-fancysig": "Сматрај потпис као викитекст (без самоповезивања)",
- "tog-uselivepreview": "Прикажи претпреглед без освежавања стране",
+ "tog-uselivepreview": "Ð\9fÑ\80икажи пÑ\80еÑ\82пÑ\80еглед без оÑ\81вежаваÑ\9aа Ñ\81Ñ\82Ñ\80аниÑ\86е",
"tog-forceeditsummary": "Упозори ме када не унесем опис измене",
"tog-watchlisthideown": "Сакриј моје измене са списка надгледања",
"tog-watchlisthidebots": "Сакриј измене ботова са списка надгледања",
"tog-watchlisthideminor": "Сакриј мање измене са списка надгледања",
"tog-watchlisthideliu": "Сакриј измене пријављених корисника са списка надгледања",
"tog-watchlistreloadautomatically": "Аутоматски освежи списак надгледања кад год се филтер измени (потребан JavaScript)",
- "tog-watchlistunwatchlinks": "Додај везе за директно додавање/уклањање ставки са списка надгледања (потребан ЈаваСкрипт)",
+ "tog-watchlistunwatchlinks": "Додај везе за директно додавање/уклањање ставки са списка надгледања (потребан JavaScript)",
"tog-watchlisthideanons": "Сакриј измене анонимних корисника са списка надгледања",
"tog-watchlisthidepatrolled": "Сакриј патролиране измене са списка надгледања",
"tog-watchlisthidecategorization": "Сакриј категоризацију страница",
"tagline": "Из {{SITENAME}}",
"help": "Помоћ",
"search": "Претражи",
- "search-ignored-headings": "#<!-- ову линију оставите онакву каква јесте --> <pre>\n# Наслови који ће бити игнорисани упитом\n# Промене су видљиве одмах након што страница са насловом буде пописана\n# Можете изнудити поновно пописивање са \"нулл\" променом\n# Синтакса је следећа:\n# * Свака врста која започиње \"#\" знаком па све до краја је коментар\n# * Свака не празна врста је тачан наслов за занемарити, у тачном облику\nРеференце\nСпољашње везе\nПогледајте\n#</pre> <!-- ову линију оставите онакву каква јесте -->",
+ "search-ignored-headings": " #<!-- не мењајте ништа у овом реду --> <pre>\n# Наслови који ће бити занемарени при претрази.\n# Измене су видљиве одмах након што се страница са насловом попише.\n# Можете изнудити поновно пописивање „нултом” изменом.\n# Синтакса је следећа:\n# * Сваки ред који започиње знаком „#” је коментар.\n# * Сваки не празни ред је тачан наслов који ће бити занемарен, с тим да се разликују мала и велика слова и све остало\nРеференце\nСпољашње везе\nТакође погледајте\n #</pre> <!-- не мењајте ништа у овом реду -->",
"searchbutton": "Претражи",
"go": "Иди",
"searcharticle": "Иди",
"cannotloginnow-title": "Пријава тренутно није могућа",
"cannotloginnow-text": "Пријава није могућа када се користи $1.",
"cannotcreateaccount-title": "Отварање налога није могуће",
+ "cannotcreateaccount-text": "Директно прављење налога није омогућено на овом викију.",
"yourdomainname": "Домен:",
"password-change-forbidden": "Не можете да промените лозинку на овом викију.",
"externaldberror": "Дошло је до грешке при препознавању базе података или немате овлашћења да ажурирате свој спољни налог.",
"createacct-email-ph": "Унесите Вашу имејл адресу",
"createacct-another-email-ph": "Унесите имејл адресу",
"createaccountmail": "Користите привремену, случајно створену лозинку и пошаљите на наведену имејл адресу",
+ "createaccountmail-help": "Може се користити да се некоме направи налог без сазнања лозинке.",
"createacct-realname": "Право име (необавезно)",
"createacct-reason": "Разлог",
"createacct-reason-ph": "Зашто правите још један налог?",
+ "createacct-reason-help": "Порука која се приказује у дневнику стварања корисничких налога",
"createacct-submit": "Отвори налог",
"createacct-another-submit": "Отвори налог",
"createacct-continue-submit": "Наставите отварање налога",
"botpasswords-label-cancel": "Откажи",
"botpasswords-label-delete": "Обриши",
"botpasswords-label-resetpassword": "Ресетуј лозинку",
+ "botpasswords-label-grants": "Применљиве дозволе:",
"botpasswords-label-grants-column": "Одобрено",
"botpasswords-bad-appid": "„$1” није исправан назив бота.",
"botpasswords-insert-failed": "Неуспешно додавање бота \"$1\". Да ли је већ додат?",
"recentchangesdays-max": "Највише $1 {{PLURAL:$1|дан|дана}}",
"recentchangescount": "Број измена за приказ:",
"prefs-help-recentchangescount": "Подразумева скорашње измене, историје страница и дневнике.",
- "prefs-help-watchlist-token2": "Ово је тајни кључ за веб-довод Вашег списка надгледања. \nСвако ко зна овај кључ биће у могућности да види Ваша списак надгледања, зато кључ немојте одавати никоме. \nАко је потребно, кључ [[Special:ResetTokens|можете ресетовати]].",
+ "prefs-help-watchlist-token2": "Ово је тајни кључ за веб-довод Вашег списка надгледања. \nСвако ко зна овај кључ биће у могућности да види Ваш списак надгледања, зато кључ немојте одавати никоме. \nАко је потребно, кључ [[Special:ResetTokens|можете ресетовати]].",
"savedprefs": "Ваша подешавања су сачувана.",
"savedrights": "Корисничке групе за {{GENDER:$1|$1}} су сачуване.",
"timezonelegend": "Временска зона:",
"email-blacklist-label": "Онемогући следећим корисницима да ми шаљу имејлове:",
"prefs-searchoptions": "Претрага",
"prefs-namespaces": "Именски простори",
- "default": "подÑ\80азÑ\83мевано",
+ "default": "подÑ\80азÑ\83мевана",
"prefs-files": "Датотеке",
"prefs-custom-css": "Прилагођени CSS",
"prefs-custom-js": "Прилагођени јаваскрипт",
"grant-group-file-interaction": "Уређивање датотека",
"grant-group-watchlist-interaction": "Уређивање вашег списка надгледања",
"grant-group-email": "Пошаљи имејл",
+ "grant-group-high-volume": "Извршавање великог броја радњи",
+ "grant-group-customization": "Прилагођавање и подешавања",
+ "grant-group-administration": "Извршавање административних радњи",
+ "grant-group-private-information": "Приступање Вашим личним подацима",
"grant-group-other": "Разне активности",
"grant-blockusers": "Блокирање и деблокирање корисника",
"grant-createaccount": "Отварање налога",
"grant-editpage": "Уређивање постојећих страница",
"grant-editprotected": "Уређивање заштићених страница",
"grant-highvolume": "Масовно уређивање",
+ "grant-oversight": "Скривање корисника и измена",
"grant-patrol": "Патролирање измена",
"grant-privateinfo": "Приступи приватним информацијама",
"grant-protect": "Закључавање и откључавање страница",
"grant-basic": "Основна права",
"grant-viewdeleted": "Преглед обрисаних страница и датотека",
"grant-viewmywatchlist": "Преглед вашег списак надгледања",
+ "grant-viewrestrictedlogs": "Прегледање ограничених уноса у дневнику",
"newuserlogpage": "Дневник нових корисника",
"newuserlogpagetext": "Ово је дневник нових корисника.",
"rightslog": "Дневник корисничких права",
"action-writeapi": "писање АПИ-ја",
"action-delete": "брисање ове странице",
"action-deleterevision": "брисање измена",
+ "action-deletelogentry": "обриши уносе у дневнику",
"action-deletedhistory": "прегледање обрисане историје странице",
+ "action-deletedtext": "прегледај обрисани текст измене",
"action-browsearchive": "претраживање обрисаних страница",
"action-undelete": "враћање страница",
"action-suppressrevision": "прегледање и враћање сакривених измена",
"rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|измена|измене}}, $2",
"rcfilters-date-popup-title": "Временски период",
"rcfilters-days-title": "Скорашњи дани",
- "rcfilters-hours-title": "СкоÑ\80аÑ\88Ñ\9aи сати",
+ "rcfilters-hours-title": "СкоÑ\80аÑ\88Ñ\9aе сати",
"rcfilters-days-show-days": "$1 {{PLURAL:$1|дан|дана}}",
"rcfilters-days-show-hours": "$1 {{PLURAL:$1|сат|сата}}",
"rcfilters-highlighted-filters-list": "Истакнуто: $1",
"uploaded-href-unsafe-target-svg": "Пронађен href са несигурним подацима: URI одредиште <code><$1 $2=\"$3\"></code> у постављеној SVG датотеци.",
"uploaded-animate-svg": "Пронађена „animate“ ознака која можда мења href користећи се „from“ атрибутом <code><$1 $2=\"$3\"></code> у постављеној SVG датотеци.",
"uploadscriptednamespace": "Ова SVG датотека садржи погрешан именски простор „<nowiki>$1</nowiki>“",
+ "uploadinvalidxml": "Није могуће рашчланити XML отпремљене датотеке.",
"uploadvirus": "Датотека садржи вирус!\nДетаљи: $1",
"uploadjava": "Датотека је формата ZIP који садржи јава .class елемент.\nСлање јава датотека није дозвољено јер оне могу изазвати заобилажење сигурносних ограничења.",
"upload-source": "Изворна датотека",
"upload-too-many-redirects": "Адреса садржи превише преусмерења",
"upload-http-error": "Дошло је до HTTP грешке: $1",
"upload-copy-upload-invalid-domain": "Примерци отпремања нису доступни на овом домену.",
- "upload-dialog-title": "Ð\9eÑ\82пÑ\80емаÑ\9aе даÑ\82оÑ\82ека",
+ "upload-dialog-title": "Ð\9eÑ\82пÑ\80еми даÑ\82оÑ\82екÑ\83",
"upload-dialog-button-cancel": "Откажи",
"upload-dialog-button-back": "Назад",
"upload-dialog-button-done": "Готово",
"upload-form-label-own-work": "Ово је моје сопствено дело",
"upload-form-label-infoform-categories": "Категорије",
"upload-form-label-infoform-date": "Датум",
+ "upload-form-label-not-own-work-local-generic-local": "Такође можете покушати [[Special:Upload|подразумевану страницу за отпремање]].",
"backend-fail-stream": "Не могу да емитујем датотеку $1.",
"backend-fail-backup": "Не могу да направим резерву датотеке $1.",
"backend-fail-notexists": "Датотека $1 не постоји.",
"uploadstash-badtoken": "Извршавање дате радње није успело, разлог томе може бити истек времена за уређивање. Покушајте поново.",
"uploadstash-errclear": "Чишћење датотека није успело.",
"uploadstash-refresh": "Освежи списак датотека",
+ "uploadstash-thumbnail": "погледај минијатуру",
"uploadstash-bad-path": "Путања не постоји.",
"uploadstash-bad-path-invalid": "Путања није исправна.",
"uploadstash-bad-path-unknown-type": "Непознат тип „$1“.",
+ "uploadstash-bad-path-unrecognized-thumb-name": "Непрепознато име минијатуре.",
+ "uploadstash-bad-path-bad-format": "Кључ „$1“ није у одговарајућем облику.",
+ "uploadstash-file-not-found-no-thumb": "Не могу добити минијатуру.",
+ "uploadstash-file-not-found-no-remote-thumb": "Добављање минијатуре није успело: $1\nАдреса = $2",
+ "uploadstash-file-not-found-missing-content-type": "Недостаје заглавље за врсту садржаја.",
+ "uploadstash-no-extension": "Нема траженог додатка.",
"invalid-chunk-offset": "Неисправна полазна тачка",
"img-auth-accessdenied": "Приступ је одбијен",
"img-auth-nopathinfo": "Недостаје PATH_INFO.\nВаш сервер није подешен да прослеђује овакве податке.\nМожда је заснован на CGI-ју који не подржава img_auth.\nПогледајте https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization?uselang=sr-ec.",
"apisandbox-sending-request": "Слање API захтева...",
"apisandbox-loading-results": "Пријем API резултата...",
"apisandbox-results-error": "Дошло је до грешке приликом учитавања резултата API упита: $1.",
+ "apisandbox-request-selectformat-label": "Прикажи сахтеване податке као:",
"apisandbox-request-url-label": "Адреса захтева:",
"apisandbox-continue": "Настави",
"apisandbox-continue-clear": "Очисти",
"tooltip-ca-move": "Премести ову страницу",
"tooltip-ca-watch": "Додај ову страницу на списак надгледања",
"tooltip-ca-unwatch": "Уклони ову страницу са списка надгледања",
- "tooltip-search": "Ð\9fÑ\80еÑ\82Ñ\80ага",
+ "tooltip-search": "Ð\9fÑ\80еÑ\82Ñ\80ажи",
"tooltip-search-go": "Идите на страницу с овим именом, ако постоји",
"tooltip-search-fulltext": "Претражите странице с овим текстом",
"tooltip-p-logo": "Посетите главну страну",
"feedback-termsofuse": "Прихватам да пошаљем повратне информације у складу са условима коришћења.",
"feedback-thanks": "Хвала! Ваша повратна информација је постављена на страницу „[$2 $1]“.",
"feedback-thanks-title": "Хвала вам!",
- "searchsuggest-search": "Ð\9fÑ\80еÑ\82Ñ\80ага",
+ "searchsuggest-search": "Ð\9fÑ\80еÑ\82Ñ\80ажи",
"searchsuggest-containing": "садржи...",
"api-error-badtoken": "Унутрашња грешка: неисправан жетон.",
"api-error-emptypage": "Стварање нових празних страница није дозвољено.",
"rollback-success": "Återställde ändringar av {{GENDER:$3|$1}};\nändrade tillbaka till senaste versionen av {{GENDER:$4|$2}}.",
"rollback-success-notify": "Återställde ändringar av $1;\nändrade tillbaka till senaste sidversion av $2. [$3 Visa ändringar]",
"sessionfailure-title": "Sessionsfel",
- "sessionfailure": "Något med din session som inloggad är på tok. Din begärda åtgärd har avbrutits, för att förhindra att någon kapar din session. Klicka på \"Tillbaka\" i din webbläsare och ladda om den sida du kom ifrån. Försök sedan igen.",
+ "sessionfailure": "Någonting med din inloggningssession är på tok;\ndin begärda åtgärd har avbrutits för att förhindra att någon kapar din session.\nSkicka formuläret igen.",
"changecontentmodel": "Ändra innehållsmodell för en sida",
"changecontentmodel-legend": "Ändra innehållsmodell",
"changecontentmodel-title-label": "Sidtitel",
"watchlistedit-clear-titles": "Sidor:",
"watchlistedit-clear-submit": "Rensa bevakningslistan (Detta är permanent!)",
"watchlistedit-clear-done": "Din bevakningslista har rensats.",
+ "watchlistedit-clear-jobqueue": "Din bevakningslista skapas. Detta kan ta en stund!",
"watchlistedit-clear-removed": "{{PLURAL:$1|1 sida|$1 sidor}} togs bort:",
"watchlistedit-too-many": "Det finns för många sidor att visa här.",
"watchlisttools-clear": "Rensa bevakningslistan",
"SF-Language",
"Urhixidur",
"아라",
- "Macofe"
+ "Macofe",
+ "Fanjiayi"
]
},
"tog-underline": "Rëddaatu lëkkalekaay yi :",
"contributions": "Cëruy bii {{GENDER:$1|jëfandikukat}}",
"contributions-title": "Cëru yu jëfandikukat bii di $1",
"mycontris": "Cëru",
+ "anoncontribs": "Cëru",
"contribsub2": "Ngir $1 ($2)",
"nocontribs": "Amul benn coppite bu melokaanoo nii bu ñu gis.",
"uctop": "(bi mujj)",
"spam_blanking": "Setal nañ wecc sumb yi amoon lëkkalekaay buy jëme $1",
"simpleantispam-label": "Caytu lànk-spam.\n<strong>Bu</strong> fi yokk lenn!",
"pageinfo-toolboxlink": "Xibaar ci xët wi",
+ "pageinfo-contentpage-yes": "Waaw",
"previousdiff": "← Coppite yi gën a yàgg",
"nextdiff": "Coppite yi mujj →",
"file-info": "Réyaayu file bi : $1, type MIME : $2",
"colon-separator": ":",
"word-separator": "",
"ellipsis": "…",
- "parentheses": " ($1)",
+ "parentheses": "($1)",
"quotation-marks": "\"$1\"",
"imgmultipageprev": "← 上一頁",
"imgmultipagenext": "下一頁 →",
"logentry-protect-protect-cascade": "$1 {{GENDER:$2|已保護}} $3 $4 [連鎖]",
"logentry-protect-modify": "$1 {{GENDER:$2|已更改}} $3 的保護層級 $4",
"logentry-protect-modify-cascade": "$1 {{GENDER:$2|已更改}} $3 的保護層級 $4 [連鎖]",
- "logentry-rights-rights": "$1 {{GENDER:$2|已更改}} {{GENDER:$6|$3}} 的群組成員資格由 $4 成為 $5",
+ "logentry-rights-rights": "$1已將{{GENDER:$6|$3}}的使用者群組從$4{{GENDER:$2|更改}}至$5",
"logentry-rights-rights-legacy": "$1 {{GENDER:$2|已更改}} $3 的群組成員資格",
"logentry-rights-autopromote": "$1 已自動{{GENDER:$2|提升}}從 $4 成為 $5",
"logentry-upload-upload": "$1 {{GENDER:$2|已上傳}} $3",
"logentry-tag-update-remove-logentry": "$1 {{GENDER:$2|已移除}}{{PLURAL:$9|標籤|標籤}} $8 自日誌項目 $3 的修訂 $5。",
"logentry-tag-update-revision": "$1 {{GENDER:$2|已更新}}標籤於頁面 $3 的修訂 $4 ({{PLURAL:$7|加入}} $6; {{PLURAL:$9|移除}} $8)。",
"logentry-tag-update-logentry": "$1 {{GENDER:$2|已更新}}標籤於頁面 $3 的日誌項目 $5 ({{PLURAL:$7|加入}} $6; {{PLURAL:$9|移除}} $8)。",
- "rightsnone": "(無)",
+ "rightsnone": "(無)",
"rightslogentry-temporary-group": "$1 (臨時,直到 $2)",
"feedback-adding": "正在新增意見回饋至頁面...",
"feedback-back": "返回",
"http://en.wikipedia.org. This is sometimes necessary because " .
"server name detection may fail in command line scripts.", false, true );
$this->addOption( 'profiler', 'Profiler output format (usually "text")', false, true );
+ // This is named --mwdebug, because --debug would conflict in the phpunit.php CLI script.
+ $this->addOption( 'mwdebug', 'Enable built-in MediaWiki development settings', false, true );
# Save generic options to display them separately in help
$this->mGenericParameters = $this->mParams;
MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->destroy();
}
+ # Apply debug settings
+ if ( $this->hasOption( 'mwdebug' ) ) {
+ require __DIR__ . '/../includes/DevelopmentSettings.php';
+ }
+
// Per-script profiling; useful for debugging
$this->activateProfiler();
'oojs',
'mediawiki.api',
'mediawiki.api.options',
+ 'mediawiki.jqueryMsg',
'mediawiki.Uri',
'mediawiki.user',
],
+ 'messages' => [
+ 'quotation-marks',
+ 'rcfilters-filterlist-title',
+ ],
],
'mediawiki.rcfilters.filters.ui' => [
'scripts' => [
'dependencies' => [
'oojs-ui-widgets',
'jquery.makeCollapsible',
+ 'mediawiki.jqueryMsg',
'mediawiki.language',
'mediawiki.user',
'mediawiki.util',
* - Firefox 4+
* - Safari 5+
* - Opera 15+
- * - Mobile Safari 5.1+ (iOS 5+)
+ * - Mobile Safari 6.0+ (iOS 6+)
* - Android 4.1+
*
* Browsers we support in our no-javascript run-time (Grade C):
$cache = \MediaWiki\MediaWikiServices::getInstance()->getMainWANObjectCache();
$jobq = JobQueueGroup::singleton();
- // Delete EditPage jobs that might have been left behind by other tests
+ // Delete jobs that might have been left behind by other tests
$jobq->get( 'htmlCacheUpdate' )->delete();
$jobq->get( 'recentChangesUpdate' )->delete();
+ $jobq->get( 'userGroupExpiry' )->delete();
$cache->delete( $cache->makeKey( 'SiteStats', 'jobscount' ) );
$jobq->push( new NullJob( Title::newMainPage(), [] ) );
use Wikimedia\Rdbms\ResultWrapper;
class DatabaseSqliteMock extends DatabaseSqlite {
- private $lastQuery;
-
public static function newInstance( array $p = [] ) {
$p['dbFilePath'] = ':memory:';
$p['schema'] = false;
}
function query( $sql, $fname = '', $tempIgnore = false ) {
- $this->lastQuery = $sql;
-
return true;
}
public function tableExists( $table, $fname = __METHOD__ ) {
$tableRaw = $this->tableName( $table, 'raw' );
- if ( isset( $this->mSessionTempTables[$tableRaw] ) ) {
+ if ( isset( $this->sessionTempTables[$tableRaw] ) ) {
return true; // already known to exist
}
* @covers \Wikimedia\Rdbms\ChronologyProtector
*/
public function testChronologyProtector() {
- // (a) First HTTP request
- $m1Pos = new MySQLMasterPos( 'db1034-bin.000976', '843431247' );
- $m2Pos = new MySQLMasterPos( 'db1064-bin.002400', '794074907' );
-
$now = microtime( true );
+ // (a) First HTTP request
+ $m1Pos = new MySQLMasterPos( 'db1034-bin.000976/843431247', $now );
+ $m2Pos = new MySQLMasterPos( 'db1064-bin.002400/794074907', $now );
+
// Master DB 1
$mockDB1 = $this->getMockBuilder( DatabaseMysqli::class )
->disableOriginalConstructor()
try {
throw new BadTitleError();
} catch ( BadTitleError $e ) {
+ ob_start();
$e->report();
- $this->assertTrue( true );
+ $text = ob_get_clean();
+ $this->assertContains( $e->getText(), $text );
}
}
try {
throw new ThrottledError();
} catch ( ThrottledError $e ) {
+ ob_start();
$e->report();
- $this->assertTrue( true );
+ $text = ob_get_clean();
+ $this->assertContains( $e->getText(), $text );
}
}
$db->listViews( '' ) );
}
+ public function testBinLogName() {
+ $pos = new MySQLMasterPos( "db1052.2424/4643", 1 );
+
+ $this->assertEquals( "db1052", $pos->binlog );
+ $this->assertEquals( "db1052.2424", $pos->getLogFile() );
+ $this->assertEquals( [ 2424, 4643 ], $pos->pos );
+ }
+
/**
* @dataProvider provideComparePositions
* @covers Wikimedia\Rdbms\MySQLMasterPos
*/
- public function testHasReached( MySQLMasterPos $lowerPos, MySQLMasterPos $higherPos, $match ) {
+ public function testHasReached(
+ MySQLMasterPos $lowerPos, MySQLMasterPos $higherPos, $match, $hetero
+ ) {
if ( $match ) {
$this->assertTrue( $lowerPos->channelsMatch( $higherPos ) );
- $this->assertTrue( $higherPos->hasReached( $lowerPos ) );
- $this->assertTrue( $higherPos->hasReached( $higherPos ) );
+ if ( $hetero ) {
+ // Each position is has one channel higher than the other
+ $this->assertFalse( $higherPos->hasReached( $lowerPos ) );
+ } else {
+ $this->assertTrue( $higherPos->hasReached( $lowerPos ) );
+ }
$this->assertTrue( $lowerPos->hasReached( $lowerPos ) );
+ $this->assertTrue( $higherPos->hasReached( $higherPos ) );
$this->assertFalse( $lowerPos->hasReached( $higherPos ) );
} else { // channels don't match
$this->assertFalse( $lowerPos->channelsMatch( $higherPos ) );
}
public static function provideComparePositions() {
+ $now = microtime( true );
+
return [
// Binlog style
[
- new MySQLMasterPos( 'db1034-bin.000976', '843431247' ),
- new MySQLMasterPos( 'db1034-bin.000976', '843431248' ),
- true
+ new MySQLMasterPos( 'db1034-bin.000976/843431247', $now ),
+ new MySQLMasterPos( 'db1034-bin.000976/843431248', $now ),
+ true,
+ false
],
[
- new MySQLMasterPos( 'db1034-bin.000976', '999' ),
- new MySQLMasterPos( 'db1034-bin.000976', '1000' ),
- true
+ new MySQLMasterPos( 'db1034-bin.000976/999', $now ),
+ new MySQLMasterPos( 'db1034-bin.000976/1000', $now ),
+ true,
+ false
],
[
- new MySQLMasterPos( 'db1034-bin.000976', '999' ),
- new MySQLMasterPos( 'db1035-bin.000976', '1000' ),
+ new MySQLMasterPos( 'db1034-bin.000976/999', $now ),
+ new MySQLMasterPos( 'db1035-bin.000976/1000', $now ),
+ false,
false
],
// MySQL GTID style
[
- new MySQLMasterPos( 'db1-bin.2', '1', '3E11FA47-71CA-11E1-9E33-C80AA9429562:23' ),
- new MySQLMasterPos( 'db1-bin.2', '2', '3E11FA47-71CA-11E1-9E33-C80AA9429562:24' ),
- true
+ new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:23', $now ),
+ new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:24', $now ),
+ true,
+ false
],
[
- new MySQLMasterPos( 'db1-bin.2', '1', '3E11FA47-71CA-11E1-9E33-C80AA9429562:99' ),
- new MySQLMasterPos( 'db1-bin.2', '2', '3E11FA47-71CA-11E1-9E33-C80AA9429562:100' ),
- true
+ new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:99', $now ),
+ new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:100', $now ),
+ true,
+ false
],
[
- new MySQLMasterPos( 'db1-bin.2', '1', '3E11FA47-71CA-11E1-9E33-C80AA9429562:99' ),
- new MySQLMasterPos( 'db1-bin.2', '2', '1E11FA47-71CA-11E1-9E33-C80AA9429562:100' ),
+ new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:99', $now ),
+ new MySQLMasterPos( '1E11FA47-71CA-11E1-9E33-C80AA9429562:100', $now ),
+ false,
false
],
// MariaDB GTID style
[
- new MySQLMasterPos( 'db1-bin.2', '1', '255-11-23' ),
- new MySQLMasterPos( 'db1-bin.2', '2', '255-11-24' ),
- true
+ new MySQLMasterPos( '255-11-23', $now ),
+ new MySQLMasterPos( '255-11-24', $now ),
+ true,
+ false
+ ],
+ [
+ new MySQLMasterPos( '255-11-99', $now ),
+ new MySQLMasterPos( '255-11-100', $now ),
+ true,
+ false
+ ],
+ [
+ new MySQLMasterPos( '255-11-999', $now ),
+ new MySQLMasterPos( '254-11-1000', $now ),
+ false,
+ false
+ ],
+ [
+ new MySQLMasterPos( '255-11-23,256-12-50', $now ),
+ new MySQLMasterPos( '255-11-24', $now ),
+ true,
+ false
+ ],
+ [
+ new MySQLMasterPos( '255-11-99,256-12-50,257-12-50', $now ),
+ new MySQLMasterPos( '255-11-1000', $now ),
+ true,
+ false
],
[
- new MySQLMasterPos( 'db1-bin.2', '1', '255-11-99' ),
- new MySQLMasterPos( 'db1-bin.2', '2', '255-11-100' ),
+ new MySQLMasterPos( '255-11-23,256-12-50', $now ),
+ new MySQLMasterPos( '255-11-24,155-52-63', $now ),
+ true,
+ false
+ ],
+ [
+ new MySQLMasterPos( '255-11-99,256-12-50,257-12-50', $now ),
+ new MySQLMasterPos( '255-11-1000,256-12-51', $now ),
+ true,
+ false
+ ],
+ [
+ new MySQLMasterPos( '255-11-99,256-12-50', $now ),
+ new MySQLMasterPos( '255-13-1000,256-14-49', $now ),
+ true,
true
],
[
- new MySQLMasterPos( 'db1-bin.2', '1', '255-11-999' ),
- new MySQLMasterPos( 'db1-bin.2', '2', '254-11-1000' ),
+ new MySQLMasterPos( '253-11-999,255-11-999', $now ),
+ new MySQLMasterPos( '254-11-1000', $now ),
+ false,
false
],
];
public function testChannelsMatch( MySQLMasterPos $pos1, MySQLMasterPos $pos2, $matches ) {
$this->assertEquals( $matches, $pos1->channelsMatch( $pos2 ) );
$this->assertEquals( $matches, $pos2->channelsMatch( $pos1 ) );
+
+ $roundtripPos = new MySQLMasterPos( (string)$pos1, 1 );
+ $this->assertEquals( (string)$pos1, (string)$roundtripPos );
}
public static function provideChannelPositions() {
+ $now = microtime( true );
+
return [
[
- new MySQLMasterPos( 'db1034-bin.000876', '44' ),
- new MySQLMasterPos( 'db1034-bin.000976', '74' ),
+ new MySQLMasterPos( 'db1034-bin.000876/44', $now ),
+ new MySQLMasterPos( 'db1034-bin.000976/74', $now ),
true
],
[
- new MySQLMasterPos( 'db1052-bin.000976', '999' ),
- new MySQLMasterPos( 'db1052-bin.000976', '1000' ),
+ new MySQLMasterPos( 'db1052-bin.000976/999', $now ),
+ new MySQLMasterPos( 'db1052-bin.000976/1000', $now ),
true
],
[
- new MySQLMasterPos( 'db1066-bin.000976', '9999' ),
- new MySQLMasterPos( 'db1035-bin.000976', '10000' ),
+ new MySQLMasterPos( 'db1066-bin.000976/9999', $now ),
+ new MySQLMasterPos( 'db1035-bin.000976/10000', $now ),
false
],
[
- new MySQLMasterPos( 'db1066-bin.000976', '9999' ),
- new MySQLMasterPos( 'trump2016.000976', '10000' ),
+ new MySQLMasterPos( 'db1066-bin.000976/9999', $now ),
+ new MySQLMasterPos( 'trump2016.000976/10000', $now ),
false
],
];
$this->assertFalse( (bool)$db->trxLevel(), "Transaction cleared." );
}
+ /**
+ * @covers Wikimedia\Rdbms\Database::getScopedLockAndFlush
+ * @covers Wikimedia\Rdbms\Database::lock
+ * @covers Wikimedia\Rdbms\Database::unlock
+ * @covers Wikimedia\Rdbms\Database::lockIsFree
+ */
public function testGetScopedLock() {
$db = $this->getMockDB( [ 'isOpen' ] );
$db->method( 'isOpen' )->willReturn( true );
+ $this->assertEquals( 0, $db->trxLevel() );
+ $this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
+ $this->assertEquals( true, $db->lock( 'x', __METHOD__ ) );
+ $this->assertEquals( false, $db->lockIsFree( 'x', __METHOD__ ) );
+ $this->assertEquals( true, $db->unlock( 'x', __METHOD__ ) );
+ $this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
+ $this->assertEquals( 0, $db->trxLevel() );
+
+ $db->setFlag( DBO_TRX );
+ $this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
+ $this->assertEquals( true, $db->lock( 'x', __METHOD__ ) );
+ $this->assertEquals( false, $db->lockIsFree( 'x', __METHOD__ ) );
+ $this->assertEquals( true, $db->unlock( 'x', __METHOD__ ) );
+ $this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
+ $db->clearFlag( DBO_TRX );
+
+ $this->assertEquals( 0, $db->trxLevel() );
+
$db->setFlag( DBO_TRX );
try {
$this->badLockingMethodImplicit( $db );
$confstr .= '!onPageRenderingHash';
}
- // Test weird historical behavior is still weird
- public function testOptionsHashEditSection() {
- $popt = ParserOptions::newCanonical();
- $popt->registerWatcher( function ( $name ) {
- $this->assertNotEquals( 'editsection', $name );
- } );
-
- $this->assertTrue( $popt->getEditSection() );
- $this->assertSame( 'canonical', $popt->optionsHash( [] ) );
- $this->assertSame( 'canonical', $popt->optionsHash( [ 'editsection' ] ) );
-
- $popt->setEditSection( false );
- $this->assertFalse( $popt->getEditSection() );
- $this->assertSame( 'canonical', $popt->optionsHash( [] ) );
- $this->assertSame( 'editsection=0', $popt->optionsHash( [ 'editsection' ] ) );
- }
-
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage Unknown parser option bogus
<?php
-use Wikimedia\TestingAccessWrapper;
-
/**
* @group Database
* ^--- trigger DB shadowing because we are using Title magic
* @covers ParserOutput::getText
* @dataProvider provideGetText
* @param array $options Options to getText()
- * @param array $poState ParserOptions state fields to set
* @param string $text Parser text
* @param string $expect Expected output
*/
- public function testGetText( $options, $poState, $text, $expect ) {
+ public function testGetText( $options, $text, $expect ) {
$this->setMwGlobals( [
'wgArticlePath' => '/wiki/$1',
'wgScriptPath' => '/w',
'wgScript' => '/w/index.php',
] );
- $this->hideDeprecated( 'ParserOutput stateful allowTOC' );
- $this->hideDeprecated( 'ParserOutput stateful enableSectionEditLinks' );
$po = new ParserOutput( $text );
-
- // Emulate Parser
- $po->setEditSectionTokens( true );
-
- if ( $poState ) {
- $wrap = TestingAccessWrapper::newFromObject( $po );
- foreach ( $poState as $key => $value ) {
- $wrap->$key = $value;
- }
- }
-
$actual = $po->getText( $options );
$this->assertSame( $expect, $actual );
}
EOF;
return [
- 'No stateless options, default state' => [
- [], [], $text, <<<EOF
-<div class="mw-parser-output"><p>Test document.
-</p>
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
-<ul>
-<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
-<li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
-<ul>
-<li class="toclevel-2 tocsection-3"><a href="#Section_2.1"><span class="tocnumber">2.1</span> <span class="toctext">Section 2.1</span></a></li>
-</ul>
-</li>
-<li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
-</ul>
-</div>
-
-<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<p>One
-</p>
-<h2><span class="mw-headline" id="Section_2">Section 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<p>Two
-</p>
-<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=3" title="Edit section: Section 2.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3>
-<p>Two point one
-</p>
-<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<p>Three
-</p></div>
-EOF
- ],
- 'No stateless options, TOC statefully disabled' => [
- [], [ 'mTOCEnabled' => false ], $text, <<<EOF
-<div class="mw-parser-output"><p>Test document.
-</p>
-
-<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<p>One
-</p>
-<h2><span class="mw-headline" id="Section_2">Section 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<p>Two
-</p>
-<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=3" title="Edit section: Section 2.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3>
-<p>Two point one
-</p>
-<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/w/index.php?title=Test_Page&action=edit&section=4" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<p>Three
-</p></div>
-EOF
- ],
- 'No stateless options, section edits statefully disabled' => [
- [], [ 'mEditSectionTokens' => false ], $text, <<<EOF
-<div class="mw-parser-output"><p>Test document.
-</p>
-<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
-<ul>
-<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a></li>
-<li class="toclevel-1 tocsection-2"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a>
-<ul>
-<li class="toclevel-2 tocsection-3"><a href="#Section_2.1"><span class="tocnumber">2.1</span> <span class="toctext">Section 2.1</span></a></li>
-</ul>
-</li>
-<li class="toclevel-1 tocsection-4"><a href="#Section_3"><span class="tocnumber">3</span> <span class="toctext">Section 3</span></a></li>
-</ul>
-</div>
-
-<h2><span class="mw-headline" id="Section_1">Section 1</span></h2>
-<p>One
-</p>
-<h2><span class="mw-headline" id="Section_2">Section 2</span></h2>
-<p>Two
-</p>
-<h3><span class="mw-headline" id="Section_2.1">Section 2.1</span></h3>
-<p>Two point one
-</p>
-<h2><span class="mw-headline" id="Section_3">Section 3</span></h2>
-<p>Three
-</p></div>
-EOF
- ],
- 'Stateless options override stateful settings' => [
- [ 'allowTOC' => true, 'enableSectionEditLinks' => true ],
- [ 'mTOCEnabled' => false, 'mEditSectionTokens' => false ],
- $text, <<<EOF
+ 'No options' => [
+ [], $text, <<<EOF
<div class="mw-parser-output"><p>Test document.
</p>
<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
</p></div>
EOF
],
- 'Statelessly disable section edit links' => [
- [ 'enableSectionEditLinks' => false ], [], $text, <<<EOF
+ 'Disable section edit links' => [
+ [ 'enableSectionEditLinks' => false ], $text, <<<EOF
<div class="mw-parser-output"><p>Test document.
</p>
<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
</p></div>
EOF
],
- 'Statelessly disable TOC' => [
- [ 'allowTOC' => false ], [], $text, <<<EOF
+ 'Disable TOC' => [
+ [ 'allowTOC' => false ], $text, <<<EOF
<div class="mw-parser-output"><p>Test document.
</p>
</p></div>
EOF
],
- 'Statelessly unwrap text' => [
- [ 'unwrap' => true ], [], $text, <<<EOF
+ 'Unwrap text' => [
+ [ 'unwrap' => true ], $text, <<<EOF
<p>Test document.
</p>
<div id="toc" class="toc"><div class="toctitle"><h2>Contents</h2></div>
EOF
],
'Unwrap without a mw-parser-output wrapper' => [
- [ 'unwrap' => true ], [], '<div class="foobar">Content</div>', '<div class="foobar">Content</div>'
+ [ 'unwrap' => true ], '<div class="foobar">Content</div>', '<div class="foobar">Content</div>'
],
'Unwrap with extra comment at end' => [
- [ 'unwrap' => true ], [], '<div class="mw-parser-output"><p>Test document.</p></div>
+ [ 'unwrap' => true ], '<div class="mw-parser-output"><p>Test document.</p></div>
<!-- Saved in parser cache... -->', '<p>Test document.</p>
<!-- Saved in parser cache... -->'
],
'Style deduplication' => [
- [], [], $dedupText, <<<EOF
+ [], $dedupText, <<<EOF
<p>This is a test document.</p>
<style data-mw-deduplicate="duplicate1">.Duplicate1 {}</style>
<link rel="mw-deduplicated-inline-style" href="mw-data:duplicate1"/>
EOF
],
'Style deduplication disabled' => [
- [ 'deduplicateStyles' => false ], [], $dedupText, $dedupText
+ [ 'deduplicateStyles' => false ], $dedupText, $dedupText
],
];
// phpcs:enable
'use-bagostuff' => false,
'use-jobqueue' => false,
'use-normal-tables' => false,
+ 'mwdebug' => false,
'reuse-db' => false,
'wiki' => false,
'profiler' => false,
private static $testGlobals = [
[
'MiserMode' => false,
- 'AllowCategorizedRecentChanges' => false,
],
[
'MiserMode' => true,
- 'AllowCategorizedRecentChanges' => true,
],
];