aria-describedby, aria-flowto, aria-label, aria-labelledby, aria-owns.
* Removed "presentation" restriction on the HTML role attribute in wikitext.
All values are now allowed for the role attribute.
+* $wgContentHandlers now also supports callbacks to create an instance of the
+ appropriate ContentHandler subclass.
+* Added $wgAuthenticationTokenVersion, which if non-null prevents the
+ user_token database field from being exposed in cookies. Setting this would
+ be a good idea, but will log out all current sessions.
=== External library changes in 1.27 ===
'ApiLogout' => __DIR__ . '/includes/api/ApiLogout.php',
'ApiMain' => __DIR__ . '/includes/api/ApiMain.php',
'ApiManageTags' => __DIR__ . '/includes/api/ApiManageTags.php',
+ 'ApiMergeHistory' => __DIR__ . '/includes/api/ApiMergeHistory.php',
'ApiMessage' => __DIR__ . '/includes/api/ApiMessage.php',
'ApiModuleManager' => __DIR__ . '/includes/api/ApiModuleManager.php',
'ApiMove' => __DIR__ . '/includes/api/ApiMove.php',
'MemcachedPhpBagOStuff' => __DIR__ . '/includes/libs/objectcache/MemcachedPhpBagOStuff.php',
'MemoizedCallable' => __DIR__ . '/includes/libs/MemoizedCallable.php',
'MemoryFileBackend' => __DIR__ . '/includes/filebackend/MemoryFileBackend.php',
+ 'MergeHistory' => __DIR__ . '/includes/MergeHistory.php',
'MergeHistoryPager' => __DIR__ . '/includes/specials/SpecialMergeHistory.php',
'MergeLogFormatter' => __DIR__ . '/includes/logging/MergeLogFormatter.php',
'MergeMessageFileList' => __DIR__ . '/maintenance/mergeMessageFileList.php',
There are some new globals that can be used to control the behavior of the ContentHandler facility:
-* $wgContentHandlers associates content model IDs with the names of the appropriate ContentHandler subclasses.
+* $wgContentHandlers associates content model IDs with the names of the appropriate ContentHandler subclasses
+ or callbacks that create an instance of the appropriate ContentHandler subclass.
* $wgNamespaceContentModels maps namespace IDs to a content model that should be the default for that namespace.
* MediaWiki version number
* @since 1.2
*/
-$wgVersion = '1.27alpha';
+$wgVersion = '1.27.0-alpha';
/**
* Name of the site. It must be changed in LocalSettings.php
/**
* Plugins for page content model handling.
- * Each entry in the array maps a model id to a class name.
+ * Each entry in the array maps a model id to a class name or callback
+ * that creates an instance of the appropriate ContentHandler subclass.
*
* @since 1.21
*/
$wgOut->addModules( 'mediawiki.action.edit.stash' );
}
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
-
# Enabled article-related sidebar, toplinks, etc.
$wgOut->setArticleRelated( true );
* Has compatibility issues on some setups, so avoid wherever possible.
* 'http': Force a full URL with http:// as the scheme.
* 'https': Force a full URL with https:// as the scheme.
+ * 'stubThreshold' => (int): Stub threshold to use when determining link classes.
* @return string HTML <a> attribute
*/
public static function link(
$target = self::normaliseSpecialPage( $target );
# If we don't know whether the page exists, let's find out.
- if ( !in_array( 'known', $options ) && !in_array( 'broken', $options ) ) {
+ if ( !in_array( 'known', $options, true ) && !in_array( 'broken', $options, true ) ) {
if ( $target->isKnown() ) {
$options[] = 'known';
} else {
}
$oldquery = array();
- if ( in_array( "forcearticlepath", $options ) && $query ) {
+ if ( in_array( "forcearticlepath", $options, true ) && $query ) {
$oldquery = $query;
$query = array();
}
# Note: we want the href attribute first, for prettiness.
$attribs = array( 'href' => self::linkUrl( $target, $query, $options ) );
- if ( in_array( 'forcearticlepath', $options ) && $oldquery ) {
+ if ( in_array( 'forcearticlepath', $options, true ) && $oldquery ) {
$attribs['href'] = wfAppendQuery( $attribs['href'], $oldquery );
}
private static function linkUrl( $target, $query, $options ) {
# We don't want to include fragments for broken links, because they
# generally make no sense.
- if ( in_array( 'broken', $options ) && $target->hasFragment() ) {
+ if ( in_array( 'broken', $options, true ) && $target->hasFragment() ) {
$target = clone $target;
$target->setFragment( '' );
}
# If it's a broken link, add the appropriate query pieces, unless
# there's already an action specified, or unless 'edit' makes no sense
# (i.e., for a nonexistent special page).
- if ( in_array( 'broken', $options ) && empty( $query['action'] )
+ if ( in_array( 'broken', $options, true ) && empty( $query['action'] )
&& !$target->isSpecialPage() ) {
$query['action'] = 'edit';
$query['redlink'] = '1';
}
- if ( in_array( 'http', $options ) ) {
+ if ( in_array( 'http', $options, true ) ) {
$proto = PROTO_HTTP;
- } elseif ( in_array( 'https', $options ) ) {
+ } elseif ( in_array( 'https', $options, true ) ) {
$proto = PROTO_HTTPS;
} else {
$proto = PROTO_RELATIVE;
global $wgUser;
$defaults = array();
- if ( !in_array( 'noclasses', $options ) ) {
+ if ( !in_array( 'noclasses', $options, true ) ) {
# Now build the classes.
$classes = array();
- if ( in_array( 'broken', $options ) ) {
+ if ( in_array( 'broken', $options, true ) ) {
$classes[] = 'new';
}
$classes[] = 'extiw';
}
- if ( !in_array( 'broken', $options ) ) { # Avoid useless calls to LinkCache (see r50387)
- $colour = self::getLinkColour( $target, $wgUser->getStubThreshold() );
+ if ( !in_array( 'broken', $options, true ) ) { # Avoid useless calls to LinkCache (see r50387)
+ $colour = self::getLinkColour(
+ $target,
+ isset( $options['stubThreshold'] ) ? $options['stubThreshold'] : $wgUser->getStubThreshold()
+ );
if ( $colour !== '' ) {
$classes[] = $colour; # mw-redirect or stub
}
if ( $target->getPrefixedText() == '' ) {
# A link like [[#Foo]]. This used to mean an empty title
# attribute, but that's silly. Just don't output a title.
- } elseif ( in_array( 'known', $options ) ) {
+ } elseif ( in_array( 'known', $options, true ) ) {
$defaults['title'] = $target->getPrefixedText();
} else {
// This ends up in parser cache!
}
$editCount = false;
- if ( in_array( 'verify', $options ) ) {
+ if ( in_array( 'verify', $options, true ) ) {
$editCount = self::getRollbackEditCount( $rev, true );
if ( $editCount === false ) {
return '';
$inner = self::buildRollbackLink( $rev, $context, $editCount );
- if ( !in_array( 'noBrackets', $options ) ) {
+ if ( !in_array( 'noBrackets', $options, true ) ) {
$inner = $context->msg( 'brackets' )->rawParams( $inner )->escaped();
}
--- /dev/null
+<?php
+
+/**
+ *
+ *
+ * Created on Dec 29, 2015
+ *
+ * Copyright © 2015 Geoffrey Mon <geofbot@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Handles the backend logic of merging the histories of two
+ * pages.
+ *
+ * @since 1.27
+ */
+class MergeHistory {
+
+ /** @const int Maximum number of revisions that can be merged at once (avoid too much slave lag) */
+ const REVISION_LIMIT = 5000;
+
+ /** @var Title Page from which history will be merged */
+ protected $source;
+
+ /** @var Title Page to which history will be merged */
+ protected $dest;
+
+ /** @var DatabaseBase Database that we are using */
+ protected $dbw;
+
+ /** @var MWTimestamp Maximum timestamp that we can use (oldest timestamp of dest) */
+ protected $maxTimestamp;
+
+ /** @var string SQL WHERE condition that selects source revisions to insert into destination */
+ protected $timeWhere;
+
+ /** @var MWTimestamp|boolean Timestamp upto which history from the source will be merged */
+ protected $timestampLimit;
+
+ /** @var integer Number of revisions merged (for Special:MergeHistory success message) */
+ protected $revisionsMerged;
+
+ /**
+ * MergeHistory constructor.
+ * @param Title $source Page from which history will be merged
+ * @param Title $dest Page to which history will be merged
+ * @param string|boolean $timestamp Timestamp up to which history from the source will be merged
+ */
+ public function __construct( Title $source, Title $dest, $timestamp = false ) {
+ // Save the parameters
+ $this->source = $source;
+ $this->dest = $dest;
+
+ // Get the database
+ $this->dbw = wfGetDB( DB_MASTER );
+
+ // Max timestamp should be min of destination page
+ $firstDestTimestamp = $this->dbw->selectField(
+ 'revision',
+ 'MIN(rev_timestamp)',
+ array( 'rev_page' => $this->dest->getArticleID() ),
+ __METHOD__
+ );
+ $this->maxTimestamp = new MWTimestamp( $firstDestTimestamp );
+
+ // Get the timestamp pivot condition
+ try {
+ if ( $timestamp ) {
+ // If we have a requested timestamp, use the
+ // latest revision up to that point as the insertion point
+ $mwTimestamp = new MWTimestamp( $timestamp );
+ $lastWorkingTimestamp = $this->dbw->selectField(
+ 'revision',
+ 'MAX(rev_timestamp)',
+ array(
+ 'rev_timestamp <= ' . $this->dbw->timestamp( $mwTimestamp ),
+ 'rev_page' => $this->source->getArticleID()
+ ),
+ __METHOD__
+ );
+ $mwLastWorkingTimestamp = new MWTimestamp( $lastWorkingTimestamp );
+
+ $timeInsert = $mwLastWorkingTimestamp;
+ $this->timestampLimit = $mwLastWorkingTimestamp;
+ } else {
+ // If we don't, merge entire source page history into the
+ // beginning of destination page history
+
+ // Get the latest timestamp of the source
+ $lastSourceTimestamp = $this->dbw->selectField(
+ array( 'page', 'revision' ),
+ 'rev_timestamp',
+ array( 'page_id' => $this->source->getArticleID(),
+ 'page_latest = rev_id'
+ ),
+ __METHOD__
+ );
+ $lasttimestamp = new MWTimestamp( $lastSourceTimestamp );
+
+ $timeInsert = $this->maxTimestamp;
+ $this->timestampLimit = $lasttimestamp;
+ }
+
+ $this->timeWhere = "rev_timestamp <= {$this->dbw->timestamp( $timeInsert )}";
+ } catch ( TimestampException $ex ) {
+ // The timestamp we got is screwed up and merge cannot continue
+ // This should be detected by $this->isValidMerge()
+ $this->timestampLimit = false;
+ }
+ }
+
+ /**
+ * Get the number of revisions that will be moved
+ * @return int
+ */
+ public function getRevisionCount() {
+ $count = $this->dbw->selectRowCount( 'revision', '1',
+ array( 'rev_page' => $this->source->getArticleID(), $this->timeWhere ),
+ __METHOD__,
+ array( 'LIMIT' => self::REVISION_LIMIT + 1 )
+ );
+
+ return $count;
+ }
+
+ /**
+ * Get the number of revisions that were moved
+ * Used in the SpecialMergeHistory success message
+ * @return int
+ */
+ public function getMergedRevisionCount() {
+ return $this->revisionsMerged;
+ }
+
+ /**
+ * Check if the merge is possible
+ * @param User $user
+ * @param string $reason
+ * @return Status
+ */
+ public function checkPermissions( User $user, $reason ) {
+ $status = new Status();
+
+ // Check if user can edit both pages
+ $errors = wfMergeErrorArrays(
+ $this->source->getUserPermissionsErrors( 'edit', $user ),
+ $this->dest->getUserPermissionsErrors( 'edit', $user )
+ );
+
+ // Convert into a Status object
+ if ( $errors ) {
+ foreach ( $errors as $error ) {
+ call_user_func_array( array( $status, 'fatal' ), $error );
+ }
+ }
+
+ // Anti-spam
+ if ( EditPage::matchSummarySpamRegex( $reason ) !== false ) {
+ // This is kind of lame, won't display nice
+ $status->fatal( 'spamprotectiontext' );
+ }
+
+ // Check mergehistory permission
+ if ( !$user->isAllowed( 'mergehistory' ) ) {
+ // User doesn't have the right to merge histories
+ $status->fatal( 'mergehistory-fail-permission' );
+ }
+
+ return $status;
+ }
+
+ /**
+ * Does various sanity checks that the merge is
+ * valid. Only things based on the two pages
+ * should be checked here.
+ *
+ * @return Status
+ */
+ public function isValidMerge() {
+ $status = new Status();
+
+ // If either article ID is 0, then revisions cannot be reliably selected
+ if ( $this->source->getArticleID() === 0 ) {
+ $status->fatal( 'mergehistory-fail-invalid-source' );
+ }
+ if ( $this->dest->getArticleID() === 0 ) {
+ $status->fatal( 'mergehistory-fail-invalid-dest' );
+ }
+
+ // Make sure page aren't the same
+ if ( $this->source->equals( $this->dest ) ) {
+ $status->fatal( 'mergehistory-fail-self-merge' );
+ }
+
+ // Make sure the timestamp is valid
+ if ( !$this->timestampLimit ) {
+ $status->fatal( 'mergehistory-fail-bad-timestamp' );
+ }
+
+ // $this->timestampLimit must be older than $this->maxTimestamp
+ if ( $this->timestampLimit > $this->maxTimestamp ) {
+ $status->fatal( 'mergehistory-fail-timestamps-overlap' );
+ }
+
+ // Check that there are not too many revisions to move
+ if ( $this->timestampLimit && $this->getRevisionCount() > self::REVISION_LIMIT ) {
+ $status->fatal( 'mergehistory-fail-toobig', Message::numParam( self::REVISION_LIMIT ) );
+ }
+
+ return $status;
+ }
+
+ /**
+ * Actually attempt the history move
+ *
+ * @todo if all versions of page A are moved to B and then a user
+ * tries to do a reverse-merge via the "unmerge" log link, then page
+ * A will still be a redirect (as it was after the original merge),
+ * though it will have the old revisions back from before (as expected).
+ * The user may have to "undo" the redirect manually to finish the "unmerge".
+ * Maybe this should delete redirects at the source page of merges?
+ *
+ * @param User $user
+ * @param string $reason
+ * @return Status status of the history merge
+ */
+ public function merge( User $user, $reason = '' ) {
+ $status = new Status();
+
+ // Check validity and permissions required for merge
+ $validCheck = $this->isValidMerge(); // Check this first to check for null pages
+ if ( !$validCheck->isOK() ) {
+ return $validCheck;
+ }
+ $permCheck = $this->checkPermissions( $user, $reason );
+ if ( !$permCheck->isOK() ) {
+ return $permCheck;
+ }
+
+ $this->dbw->update(
+ 'revision',
+ array( 'rev_page' => $this->dest->getArticleID() ),
+ array( 'rev_page' => $this->source->getArticleID(), $this->timeWhere ),
+ __METHOD__
+ );
+
+ // Check if this did anything
+ $this->revisionsMerged = $this->dbw->affectedRows();
+ if ( $this->revisionsMerged < 1 ) {
+ $status->fatal( 'mergehistory-fail-no-change' );
+ return $status;
+ }
+
+ // Make the source page a redirect if no revisions are left
+ $haveRevisions = $this->dbw->selectField(
+ 'revision',
+ 'rev_timestamp',
+ array( 'rev_page' => $this->source->getArticleID() ),
+ __METHOD__,
+ array( 'FOR UPDATE' )
+ );
+ if ( !$haveRevisions ) {
+ if ( $reason ) {
+ $reason = wfMessage(
+ 'mergehistory-comment',
+ $this->source->getPrefixedText(),
+ $this->dest->getPrefixedText(),
+ $reason
+ )->inContentLanguage()->text();
+ } else {
+ $reason = wfMessage(
+ 'mergehistory-autocomment',
+ $this->source->getPrefixedText(),
+ $this->dest->getPrefixedText()
+ )->inContentLanguage()->text();
+ }
+
+ $contentHandler = ContentHandler::getForTitle( $this->source );
+ $redirectContent = $contentHandler->makeRedirectContent(
+ $this->dest,
+ wfMessage( 'mergehistory-redirect-text' )->inContentLanguage()->plain()
+ );
+
+ if ( $redirectContent ) {
+ $redirectPage = WikiPage::factory( $this->source );
+ $redirectRevision = new Revision( array(
+ 'title' => $this->source,
+ 'page' => $this->source->getArticleID(),
+ 'comment' => $reason,
+ 'content' => $redirectContent ) );
+ $redirectRevision->insertOn( $this->dbw );
+ $redirectPage->updateRevisionOn( $this->dbw, $redirectRevision );
+
+ // Now, we record the link from the redirect to the new title.
+ // It should have no other outgoing links...
+ $this->dbw->delete(
+ 'pagelinks',
+ array( 'pl_from' => $this->dest->getArticleID() ),
+ __METHOD__
+ );
+ $this->dbw->insert( 'pagelinks',
+ array(
+ 'pl_from' => $this->dest->getArticleID(),
+ 'pl_from_namespace' => $this->dest->getNamespace(),
+ 'pl_namespace' => $this->dest->getNamespace(),
+ 'pl_title' => $this->dest->getDBkey() ),
+ __METHOD__
+ );
+ } else {
+ // Warning if we couldn't create the redirect
+ $status->warning( 'mergehistory-warning-redirect-not-created' );
+ }
+ } else {
+ $this->source->invalidateCache(); // update histories
+ }
+ $this->dest->invalidateCache(); // update histories
+
+ // Update our logs
+ $logEntry = new ManualLogEntry( 'merge', 'merge' );
+ $logEntry->setPerformer( $user );
+ $logEntry->setComment( $reason );
+ $logEntry->setTarget( $this->source );
+ $logEntry->setParameters( array(
+ '4::dest' => $this->dest->getPrefixedText(),
+ '5::mergepoint' => $this->timestampLimit->getTimestamp( TS_MW )
+ ) );
+ $logId = $logEntry->insert();
+ $logEntry->publish( $logId );
+
+ Hooks::run( 'ArticleMergeComplete', array( $this->source, $this->dest ) );
+
+ return $status;
+ }
+}
* @return ParserOptions
*/
public function parserOptions( $options = null ) {
+ if ( $options !== null && !empty( $options->isBogus ) ) {
+ // 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 );
+ if ( !$options->matches( $anonPO ) ) {
+ wfLogWarning( __METHOD__ . ': Setting a changed bogus ParserOptions: ' . wfGetAllCallers( 5 ) );
+ $options->isBogus = false;
+ }
+ }
+
if ( !$this->mParserOptions ) {
+ if ( !$this->getContext()->getUser()->isSafeToLoad() ) {
+ // $wgUser isn't unstubbable yet, so don't try to get a
+ // ParserOptions for it. And don't cache this ParserOptions
+ // either.
+ $po = ParserOptions::newFromAnon();
+ $po->setEditSection( false );
+ $po->isBogus = true;
+ if ( $options !== null ) {
+ $this->mParserOptions = empty( $options->isBogus ) ? $options : null;
+ }
+ return $po;
+ }
+
$this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
$this->mParserOptions->setEditSection( false );
}
- return wfSetVar( $this->mParserOptions, $options );
+
+ if ( $options !== null && !empty( $options->isBogus ) ) {
+ // They're trying to restore the bogus pre-$wgUser PO. Do the right
+ // thing.
+ return wfSetVar( $this->mParserOptions, null, true );
+ } else {
+ return wfSetVar( $this->mParserOptions, $options );
+ }
}
/**
public function show() {
$this->useTransactionalTimeLimit();
+ $out = $this->getOutput();
+ $out->setRobotPolicy( 'noindex,nofollow' );
if ( $this->getContext()->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
- $out = $this->getOutput();
$out->addModuleStyles( array(
'mediawiki.ui.input',
'mediawiki.ui.checkbox',
'revisiondelete' => 'ApiRevisionDelete',
'managetags' => 'ApiManageTags',
'tag' => 'ApiTag',
+ 'mergehistory' => 'ApiMergeHistory',
);
/**
--- /dev/null
+<?php
+/**
+ *
+ *
+ * Created on Dec 29, 2015
+ *
+ * Copyright © 2015 Geoffrey Mon <geofbot@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * API Module to merge page histories
+ * @ingroup API
+ */
+class ApiMergeHistory extends ApiBase {
+
+ public function execute() {
+ $this->useTransactionalTimeLimit();
+
+ $user = $this->getUser();
+ $params = $this->extractRequestParams();
+
+ $this->requireOnlyOneParameter( $params, 'from', 'fromid' );
+ $this->requireOnlyOneParameter( $params, 'to', 'toid' );
+
+ // Get page objects (nonexistant pages get caught in MergeHistory::isValidMerge())
+ if ( isset( $params['from'] ) ) {
+ $fromTitle = Title::newFromText( $params['from'] );
+ if ( !$fromTitle || $fromTitle->isExternal() ) {
+ $this->dieUsageMsg( array( 'invalidtitle', $params['from'] ) );
+ }
+ } elseif ( isset( $params['fromid'] ) ) {
+ $fromTitle = Title::newFromID( $params['fromid'] );
+ if ( !$fromTitle ) {
+ $this->dieUsageMsg( array( 'nosuchpageid', $params['fromid'] ) );
+ }
+ }
+
+ if ( isset( $params['to'] ) ) {
+ $toTitle = Title::newFromText( $params['to'] );
+ if ( !$toTitle || $toTitle->isExternal() ) {
+ $this->dieUsageMsg( array( 'invalidtitle', $params['to'] ) );
+ }
+ } elseif ( isset( $params['toid'] ) ) {
+ $toTitle = Title::newFromID( $params['toid'] );
+ if ( !$toTitle ) {
+ $this->dieUsageMsg( array( 'nosuchpageid', $params['toid'] ) );
+ }
+ }
+
+ $reason = $params['reason'];
+ $timestamp = $params['timestamp'];
+
+ // Merge!
+ $status = $this->merge( $fromTitle, $toTitle, $timestamp, $reason );
+ if ( !$status->isOK() ) {
+ $this->dieStatus( $status );
+ }
+
+ $r = array(
+ 'from' => $fromTitle->getPrefixedText(),
+ 'to' => $toTitle->getPrefixedText(),
+ 'timestamp' => wfTimestamp( TS_ISO_8601, $params['timestamp'] ),
+ 'reason' => $params['reason']
+ );
+ $result = $this->getResult();
+
+ $result->addValue( null, $this->getModuleName(), $r );
+ }
+
+ /**
+ * @param Title $from
+ * @param Title $to
+ * @param string $timestamp
+ * @param string $reason
+ * @return Status
+ */
+ protected function merge( Title $from, Title $to, $timestamp, $reason ) {
+ $mh = new MergeHistory( $from, $to, $timestamp );
+
+ return $mh->merge( $this->getUser(), $reason );
+ }
+
+ public function mustBePosted() {
+ return true;
+ }
+
+ public function isWriteMode() {
+ return true;
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'from' => null,
+ 'fromid' => array(
+ ApiBase::PARAM_TYPE => 'integer'
+ ),
+ 'to' => null,
+ 'toid' => array(
+ ApiBase::PARAM_TYPE => 'integer'
+ ),
+ 'timestamp' => array(
+ ApiBase::PARAM_TYPE => 'timestamp'
+ ),
+ 'reason' => '',
+ );
+ }
+
+ public function needsToken() {
+ return 'csrf';
+ }
+
+ protected function getExamplesMessages() {
+ return array(
+ 'action=mergehistory&from=Oldpage&to=Newpage&token=123ABC&' .
+ 'reason=Reason'
+ => 'apihelp-mergehistory-example-merge',
+ 'action=mergehistory&from=Oldpage&to=Newpage&token=123ABC&' .
+ 'reason=Reason×tamp=2015-12-31T04%3A37%3A41Z' // TODO
+ => 'apihelp-mergehistory-example-merge-timestamp',
+ );
+ }
+
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Mergehistory';
+ }
+}
"apihelp-managetags-example-activate": "Activate a tag named <kbd>spam</kbd> with the reason <kbd>For use in edit patrolling</kbd>",
"apihelp-managetags-example-deactivate": "Deactivate a tag named <kbd>spam</kbd> with the reason <kbd>No longer required</kbd>",
+ "apihelp-mergehistory-description": "Merge page histories.",
+ "apihelp-mergehistory-param-from": "Title of the page from which history will be merged. Cannot be used together with <var>$1fromid</var>.",
+ "apihelp-mergehistory-param-fromid": "Page ID of the page from which history will be merged. Cannot be used together with <var>$1from</var>.",
+ "apihelp-mergehistory-param-to": "Title of the page to which history will be merged. Cannot be used together with <var>$1toid</var>.",
+ "apihelp-mergehistory-param-toid": "Page ID of the page to which history will be merged. Cannot be used together with <var>$1to</var>.",
+ "apihelp-mergehistory-param-timestamp": "Timestamp up to which revisions will be moved from the source page's history to the destination page's history. If omitted, the entire page history of the source page will be merged into the destination page.",
+ "apihelp-mergehistory-param-reason": "Reason for the history merge.",
+ "apihelp-mergehistory-example-merge": "Merge the entire history of <kbd>Oldpage</kbd> into <kbd>Newpage</kbd>.",
+ "apihelp-mergehistory-example-merge-timestamp": "Merge the page revisions of <kbd>Oldpage</kbd> dating up to <kbd>2015-12-31T04:37:41Z</kbd> into <kbd>Newpage</kbd>.",
+
"apihelp-move-description": "Move a page.",
"apihelp-move-param-from": "Title of the page to rename. Cannot be used together with <var>$1fromid</var>.",
"apihelp-move-param-fromid": "Page ID of the page to rename. Cannot be used together with <var>$1from</var>.",
"api-help-datatypes-header": "Datentypen",
"api-help-param-type-user": "Typ: {{PLURAL:$1|1=Benotzernumm|2=Lëscht vu Benotzernimm}}",
"api-help-examples": "{{PLURAL:$1|Beispill|Beispiler}}:",
- "api-help-permissions": "{{PLURAL:$1|Autorisatioun|Autorisatiounen}}:"
+ "api-help-permissions": "{{PLURAL:$1|Autorisatioun|Autorisatiounen}}:",
+ "api-help-open-in-apisandbox": "<small>[an der Sandkëscht opmaachen]</small>"
}
"apihelp-managetags-example-delete": "{{doc-apihelp-example|managetags|info={{doc-important|The text \"vandlaism\" in this message is intentionally misspelled; the example being documented by this message is the deletion of a misspelled tag.}}}}",
"apihelp-managetags-example-activate": "{{doc-apihelp-example|managetags}}",
"apihelp-managetags-example-deactivate": "{{doc-apihelp-example|managetags}}",
+ "apihelp-mergehistory-description": "{{doc-apihelp-description|mergehistory}}",
+ "apihelp-mergehistory-param-from": "{{doc-apihelp-param|mergehistory|from}}",
+ "apihelp-mergehistory-param-fromid": "{{doc-apihelp-param|mergehistory|fromid}}",
+ "apihelp-mergehistory-param-to": "{{doc-apihelp-param|mergehistory|to}}",
+ "apihelp-mergehistory-param-toid": "{{doc-apihelp-param|mergehistory|toid}}",
+ "apihelp-mergehistory-param-timestamp": "{{doc-apihelp-param|mergehistory|timestamp}}",
+ "apihelp-mergehistory-param-reason": "{{doc-apihelp-param|mergehistory|reason}}",
+ "apihelp-mergehistory-example-merge": "{{doc-apihelp-example|mergehistory}}",
+ "apihelp-mergehistory-example-merge-timestamp": "{{doc-apihelp-example|mergehistory}}",
"apihelp-move-description": "{{doc-apihelp-description|move}}",
"apihelp-move-param-from": "{{doc-apihelp-param|move|from}}",
"apihelp-move-param-fromid": "{{doc-apihelp-param|move|fromid}}",
"RyRubyy",
"Umherirrender",
"Apflu",
- "Hzy980512"
+ "Hzy980512",
+ "PhiLiP"
]
},
"apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|文档]]\n* [[mw:API:FAQ|常见问题]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api 邮件列表]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API公告]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R 程序错误与功能请求]\n</div>\n<strong>状态信息:</strong>本页所展示的所有特性都应正常工作,但是API仍在开发当中,将会随时变化。请订阅[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce 邮件列表]以便获得更新通知。\n\n<strong>错误请求:</strong>当API收到错误请求时,HTTP header将会返回一个包含\"MediaWiki-API-Error\"的值,随后header的值与error code将会送回并设置为相同的值。详细信息请参阅[[mw:API:Errors_and_warnings|API: 错误与警告]]。\n\n<strong>测试中:</strong>测试API请求的易用性,请参见[[Special:ApiSandbox]]。",
"apihelp-query+allrevisions-param-generatetitles": "当作为生成器使用时,生成标题而不是修订ID。",
"apihelp-query+allrevisions-example-user": "列出由用户<kbd>Example</kbd>作出的最近50次贡献。",
"apihelp-query+allrevisions-example-ns-main": "列举主名字空间中的前50次修订。",
- "apihelp-query+mystashedfiles-description": "获取当前用户的上传藏匿中的文件列表。",
+ "apihelp-query+mystashedfiles-description": "获取当前用户上传暂存库中的文件列表。",
"apihelp-query+mystashedfiles-param-prop": "要检索文件的属性。",
"apihelp-query+mystashedfiles-paramvalue-prop-size": "检索文件大小和图片尺寸。",
"apihelp-query+mystashedfiles-paramvalue-prop-type": "检索文件的MIME类型和媒体类型。",
- "apihelp-query+mystashedfiles-param-limit": "获取多少文件。",
+ "apihelp-query+mystashedfiles-param-limit": "要获取文件的数量。",
+ "apihelp-query+mystashedfiles-example-simple": "获取当前用户上传暂存库中的文件的filekey、大小和像素尺寸。",
"apihelp-query+alltransclusions-description": "列出所有嵌入页面(使用{{x}}嵌入的页面),包括不存在的。",
"apihelp-query+alltransclusions-param-from": "要列举的起始嵌入标题。",
"apihelp-query+alltransclusions-param-to": "要列举的最终嵌入标题。",
"apihelp-query+imageinfo-param-urlwidth": "如果$2prop=url被设定,将返回至缩放到此宽度的一张图片的URL。\n由于性能原因,如果此消息被使用,将不会返回超过$1张被缩放的图片。",
"apihelp-query+imageinfo-param-urlheight": "与$1urlwidth类似。",
"apihelp-query+imageinfo-param-metadataversion": "要使用的元数据版本。如果<kbd>latest</kbd>被指定,则使用最新版本。默认为<kbd>1</kbd>以便向下兼容。",
- "apihelp-query+imageinfo-param-extmetadatalanguage": "要取得extmetadata的语言。This affects both which translation to fetch, if multiple are available, as well as how things like numbers and various values are formatted.",
+ "apihelp-query+imageinfo-param-extmetadatalanguage": "要取得extmetadata的语言。这会影响到抓取翻译的选择,如果有多个可用的话,还会影响到数字等数值的格式。",
"apihelp-query+imageinfo-param-extmetadatamultilang": "如果用于extmetadata属性的翻译可用,则全部取得。",
"apihelp-query+imageinfo-param-extmetadatafilter": "如果指定且非空,则只为$1prop=extmetadata返回这些键。",
"apihelp-query+imageinfo-param-urlparam": "处理器特定的参数字符串。例如PDF可能使用<kbd>page15-100px</kbd>。<var>$1urlwidth</var>必须被使用,并与<var>$1urlparam</var>一致。",
"apihelp-query+redirects-param-show": "只显示符合这些标准的项目:\n;fragment:只显示带碎片的重定向。\n;!fragment:只显示不带碎片的重定向。",
"apihelp-query+redirects-example-simple": "获取至[[Main Page]]的重定向列表。",
"apihelp-query+redirects-example-generator": "获取所有重定向至[[Main Page]]的信息。",
- "apihelp-query+revisions-description": "获取修订版本信息。\n\n可用于以下几个方面:\n# Get data about a set of pages (last revision), by setting titles or pageids.\n# Get revisions for one given page, by using titles or pageids with start, end, or limit.\n# Get data about a set of revisions by setting their IDs with revids.",
+ "apihelp-query+revisions-description": "获取修订版本信息。\n\n可用于以下几个方面:\n# 通过设置title或pageid获取一批页面(最新修订)的数据。\n# 通过使用带start、end或limit的title或pageid获取给定页面的多个修订。\n# 通过revid设置一批修订的ID获取它们的数据。",
"apihelp-query+revisions-paraminfo-singlepageonly": "可能只能与单一页面使用(模式#2)。",
"apihelp-query+revisions-param-startid": "从哪个修订版本ID开始列举。",
"apihelp-query+revisions-param-endid": "在此修订版本ID停止修订列举。",
"apihelp-query+usercontribs-param-start": "返回的起始时间戳。",
"apihelp-query+usercontribs-param-end": "返回的最终时间戳。",
"apihelp-query+usercontribs-param-user": "要检索贡献的用户。",
+ "apihelp-query+usercontribs-param-userprefix": "取得所有用户名以这个值开头的用户的贡献。覆盖$1user。",
"apihelp-query+usercontribs-param-namespace": "只列出这些名字空间的贡献。",
"apihelp-query+usercontribs-param-prop": "包含额外的信息束:",
"apihelp-query+usercontribs-paramvalue-prop-ids": "添加页面ID和修订ID。",
"apihelp-watch-example-unwatch": "取消监视页面<kbd>Main Page</kbd>。",
"apihelp-watch-example-generator": "监视主名字空间中的最少几个页面。",
"apihelp-format-example-generic": "返回查询结果为$1格式。",
+ "apihelp-format-param-wrappedhtml": "作为一个JSON对象返回渲染好的HTML和关联的ResouceLoader模块。",
"apihelp-json-description": "输出数据为JSON格式。",
"apihelp-json-param-callback": "如果指定,将输出内容包裹在一个指定的函数调用中。出于安全考虑,所有用户相关的数据将被限制。",
"apihelp-json-param-utf8": "如果指定,使用十六进制转义序列将大多数(但不是全部)非ASCII的字符编码为UTF-8,而不是替换它们。默认当<var>formatversion</var>不是<kbd>1</kbd>时。",
throw new MWException( "ContentHandlerForModelID must supply a ContentHandler instance" );
}
} else {
- $class = $wgContentHandlers[$modelId];
- $handler = new $class( $modelId );
+ $classOrCallback = $wgContentHandlers[$modelId];
+
+ if ( is_callable( $classOrCallback ) ) {
+ $handler = call_user_func( $classOrCallback, $modelId );
+ } else {
+ $handler = new $classOrCallback( $modelId );
+ }
if ( !( $handler instanceof ContentHandler ) ) {
- throw new MWException( "$class from \$wgContentHandlers is not " .
+ throw new MWException( "$classOrCallback from \$wgContentHandlers is not " .
"compatible with ContentHandler" );
}
}
namespace MediaWiki\Logger\Monolog;
/**
- * Injects `wfHostname()` and `wfWikiID()` in all records.
+ * Injects `wfHostname()`, `wfWikiID()` and `$wgVersion` in all records.
*
* @since 1.25
* @author Bryan Davis <bd808@wikimedia.org>
* @return array
*/
public function __invoke( array $record ) {
+ global $wgVersion;
$record['extra'] = array_merge(
$record['extra'],
array(
'host' => wfHostname(),
'wiki' => wfWikiID(),
+ 'mwversion' => $wgVersion,
)
);
return $record;
// $wgLogo is probably wrong (bug 48084); set something that will work.
// Single quotes work fine here, as LocalSettingsGenerator outputs this unescaped.
'wgLogo' => '$wgResourceBasePath/resources/assets/wiki.png',
+ 'wgAuthenticationTokenVersion' => 1,
);
/**
'wgRightsText', '_MainCacheType', 'wgEnableUploads',
'_MemCachedServers', 'wgDBserver', 'wgDBuser',
'wgDBpassword', 'wgUseInstantCommons', 'wgUpgradeKey', 'wgDefaultSkin',
- 'wgMetaNamespace', 'wgLogo',
+ 'wgMetaNamespace', 'wgLogo', 'wgAuthenticationTokenVersion',
),
$db->getGlobalNames()
);
\$wgSecretKey = \"{$this->values['wgSecretKey']}\";
+# Changing this will log out all existing sessions.
+\$wgAuthenticationTokenVersion = \"{$this->values['wgAuthenticationTokenVersion']}\";
+
# Site upgrade key. Must be set to a string (default provided) to turn on the
# web installer while LocalSettings.php is in place
\$wgUpgradeKey = \"{$this->values['wgUpgradeKey']}\";
// All good
$this->setVar( '_ExistingDBSettings', true );
+ // Copy $wgAuthenticationTokenVersion too, if it exists
+ $this->setVar( 'wgAuthenticationTokenVersion',
+ isset( $vars['wgAuthenticationTokenVersion'] )
+ ? $vars['wgAuthenticationTokenVersion']
+ : null
+ );
+
return $status;
}
$changed = !$content->equals( $oldContent );
+ $dbw = wfGetDB( DB_MASTER );
+
if ( $changed ) {
$prepStatus = $content->prepareSave( $this, $flags, $oldid, $user );
$status->merge( $prepStatus );
return $status;
}
- $dbw = wfGetDB( DB_MASTER );
- $dbw->begin( __METHOD__ );
+ $dbw->startAtomic( __METHOD__ );
// Get the latest page_latest value while locking it.
// Do a CAS style check to see if it's the same as when this method
// started. If it changed then bail out before touching the DB.
$latestNow = $this->lockAndGetLatest();
if ( $latestNow != $oldid ) {
- $dbw->commit( __METHOD__ );
+ $dbw->endAtomic( __METHOD__ );
// Page updated or deleted in the mean time
$status->fatal( 'edit-conflict' );
$user->incEditCount();
- $dbw->commit( __METHOD__ );
+ $dbw->endAtomic( __METHOD__ );
$this->mTimestamp = $now;
} else {
// Bug 32948: revision ID must be set to page {{REVISIONID}} and
$revision->setId( $this->getLatest() );
}
- // Update links tables, site stats, etc.
- $this->doEditUpdates(
- $revision,
- $user,
- array(
- 'changed' => $changed,
- 'oldcountable' => $meta['oldCountable'],
- 'oldrevision' => $meta['oldRevision']
- )
- );
-
if ( $changed ) {
// Return the new revision to the caller
$status->value['revision'] = $revision;
$this->mTitle->invalidateCache( $now );
}
- // Trigger post-save hook
- $hook_args = array( &$this, &$user, $content, $summary,
- $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $meta['baseRevId'] );
- ContentHandler::runLegacyHooks( 'ArticleSaveComplete', $hook_args );
- Hooks::run( 'PageContentSaveComplete', $hook_args );
+ // Do secondary updates once the main changes have been committed...
+ $that = $this;
+ $dbw->onTransactionIdle(
+ function () use (
+ $dbw, &$that, $revision, &$user, $content, $summary, &$flags,
+ $changed, $meta, &$status
+ ) {
+ // Do per-page updates in a transaction
+ $dbw->setFlag( DBO_TRX );
+ // Update links tables, site stats, etc.
+ $that->doEditUpdates(
+ $revision,
+ $user,
+ array(
+ 'changed' => $changed,
+ 'oldcountable' => $meta['oldCountable'],
+ 'oldrevision' => $meta['oldRevision']
+ )
+ );
+ // Trigger post-save hook
+ $params = array( &$that, &$user, $content, $summary, $flags & EDIT_MINOR,
+ null, null, &$flags, $revision, &$status, $meta['baseRevId'] );
+ ContentHandler::runLegacyHooks( 'ArticleSaveComplete', $params );
+ Hooks::run( 'PageContentSaveComplete', $params );
+ }
+ );
return $status;
}
# Make interwiki link HTML
$output = $this->parent->getOutput();
$replacePairs = array();
+ $options = array(
+ 'stubThreshold' => $this->parent->getOptions()->getStubThreshold(),
+ );
foreach ( $this->interwikis as $key => $link ) {
- $replacePairs[$key] = Linker::link( $link['title'], $link['text'] );
+ $replacePairs[$key] = Linker::link( $link['title'], $link['text'], array(), array(), $options );
$output->addInterwikiLink( $link['title'] );
}
$replacer = new HashtableReplacer( $replacePairs, 1 );
array_keys( $metadata )
);
if ( $missingKeys ) {
- $this->logger->info( "Session $info: Missing metadata: " . join( ', ', $missingKeys ) );
+ $this->logger->info( 'Session "{session}": Missing metadata: {missing}', array(
+ 'session' => $info,
+ 'missing' => join( ', ', $missingKeys ),
+ ) );
return false;
}
$bp = BotPassword::newFromCentralId( $metadata['centralId'], $metadata['appId'] );
if ( !$bp ) {
$this->logger->info(
- "Session $info: No BotPassword for {$metadata['centralId']} {$metadata['appId']}"
- );
+ 'Session "{session}": No BotPassword for {centralId} {appId}',
+ array(
+ 'session' => $info,
+ 'centralId' => $metadata['centralId'],
+ 'appId' => $metadata['appId'],
+ ) );
return false;
}
if ( !hash_equals( $metadata['token'], $bp->getToken() ) ) {
- $this->logger->info( "Session $info: BotPassword token check failed" );
+ $this->logger->info( 'Session "{session}": BotPassword token check failed', array(
+ 'session' => $info,
+ 'centralId' => $metadata['centralId'],
+ 'appId' => $metadata['appId'],
+ ) );
return false;
}
$status = $bp->getRestrictions()->check( $request );
if ( !$status->isOk() ) {
- $this->logger->info( "Session $info: Restrictions check failed", $status->getValue() );
+ $this->logger->info(
+ 'Session "{session}": Restrictions check failed',
+ array(
+ 'session' => $info,
+ 'restrictions' => $status->getValue(),
+ 'centralId' => $metadata['centralId'],
+ 'appId' => $metadata['appId'],
+ ) );
return false;
}
// Sanity check
if ( $userName !== null && $userInfo->getName() !== $userName ) {
+ $this->logger->warning(
+ 'Session "{session}" requested with mismatched UserID and UserName cookies.',
+ array(
+ 'session' => $info['id'],
+ 'mismatch' => array(
+ 'userid' => $userId,
+ 'cookie_username' => $userName,
+ 'username' => $userInfo->getName(),
+ ),
+ ) );
return null;
}
if ( $token !== null ) {
if ( !hash_equals( $userInfo->getToken(), $token ) ) {
+ $this->logger->warning(
+ 'Session "{session}" requested with invalid Token cookie.',
+ array(
+ 'session' => $info['id'],
+ 'userid' => $userId,
+ 'username' => $userInfo->getName(),
+ ) );
return null;
}
$info['userInfo'] = $userInfo->verified();
}
} elseif ( isset( $info['id'] ) ) {
// No UserID cookie, so insist that the session is anonymous.
+ // Note: this event occurs for several normal activities:
+ // * anon visits Special:UserLogin
+ // * anon browsing after seeing Special:UserLogin
+ // * anon browsing after edit or preview
+ $this->logger->debug(
+ 'Session "{session}" requested without UserID cookie',
+ array(
+ 'session' => $info['id'],
+ ) );
$info['userInfo'] = UserInfo::newAnonymous();
} else {
// No session ID and no user is the same as an empty session, so
// This can happen under normal circumstances, if the session exists but is
// invalid. Let's emit a log warning instead of a PHP warning.
$this->logger->warning(
- __METHOD__ . ": Session \"$id\" cannot be loaded, skipping write."
- );
+ __METHOD__ . ': Session "{session}" cannot be loaded, skipping write.',
+ array(
+ 'session' => $id,
+ ) );
return true;
}
$this->data = array();
$this->dataDirty = true;
$this->metaDirty = true;
- $this->logger->debug( "SessionBackend $this->id is unsaved, marking dirty in constructor" );
+ $this->logger->debug(
+ 'SessionBackend "{session}" is unsaved, marking dirty in constructor',
+ array(
+ 'session' => $this->id,
+ ) );
} else {
$this->data = $blob['data'];
if ( isset( $blob['metadata']['loggedOut'] ) ) {
} else {
$this->metaDirty = true;
$this->logger->debug(
- "SessionBackend $this->id metadata dirty due to missing expiration timestamp"
- );
+ 'SessionBackend "{session}" metadata dirty due to missing expiration timestamp',
+ array(
+ 'session' => $this->id,
+ ) );
}
}
$this->dataHash = md5( serialize( $this->data ) );
$this->provider->sessionIdWasReset( $this, $oldId );
$this->metaDirty = true;
$this->logger->debug(
- "SessionBackend $this->id metadata dirty due to ID reset (formerly $oldId)"
- );
+ 'SessionBackend "{session}" metadata dirty due to ID reset (formerly "{oldId}")',
+ array(
+ 'session' => $this->id,
+ 'oldId' => $oldId,
+ ) );
if ( $restart ) {
session_id( (string)$this->id );
$this->persist = true;
$this->forcePersist = true;
$this->metaDirty = true;
- $this->logger->debug( "SessionBackend $this->id force-persist due to persist()" );
+ $this->logger->debug(
+ 'SessionBackend "{session}" force-persist due to persist()',
+ array(
+ 'session' => $this->id,
+ ) );
$this->autosave();
} else {
$this->renew();
if ( $this->remember !== (bool)$remember ) {
$this->remember = (bool)$remember;
$this->metaDirty = true;
- $this->logger->debug( "SessionBackend $this->id metadata dirty due to remember-user change" );
+ $this->logger->debug(
+ 'SessionBackend "{session}" metadata dirty due to remember-user change',
+ array(
+ 'session' => $this->id,
+ ) );
$this->autosave();
}
}
$this->user = $user;
$this->metaDirty = true;
- $this->logger->debug( "SessionBackend $this->id metadata dirty due to user change" );
+ $this->logger->debug(
+ 'SessionBackend "{session}" metadata dirty due to user change',
+ array(
+ 'session' => $this->id,
+ ) );
$this->autosave();
}
if ( $this->forceHTTPS !== (bool)$force ) {
$this->forceHTTPS = (bool)$force;
$this->metaDirty = true;
- $this->logger->debug( "SessionBackend $this->id metadata dirty due to force-HTTPS change" );
+ $this->logger->debug(
+ 'SessionBackend "{session}" metadata dirty due to force-HTTPS change',
+ array(
+ 'session' => $this->id,
+ ) );
$this->autosave();
}
}
$this->loggedOut = $ts;
$this->metaDirty = true;
$this->logger->debug(
- "SessionBackend $this->id metadata dirty due to logged-out-timestamp change"
- );
+ 'SessionBackend "{session}" metadata dirty due to logged-out-timestamp change',
+ array(
+ 'session' => $this->id,
+ ) );
$this->autosave();
}
}
$this->providerMetadata = $metadata;
$this->metaDirty = true;
$this->logger->debug(
- "SessionBackend $this->id metadata dirty due to provider metadata change"
- );
+ 'SessionBackend "{session}" metadata dirty due to provider metadata change',
+ array(
+ 'session' => $this->id,
+ ) );
$this->autosave();
}
}
$data[$key] = $value;
$this->dataDirty = true;
$this->logger->debug(
- "SessionBackend $this->id data dirty due to addData(): " . wfGetAllCallers( 5 )
- );
+ 'SessionBackend "{session}" data dirty due to addData(): {callers}',
+ array(
+ 'session' => $this->id,
+ 'callers' => wfGetAllCallers( 5 ),
+ ) );
}
}
}
public function dirty() {
$this->dataDirty = true;
$this->logger->debug(
- "SessionBackend $this->id data dirty due to dirty(): " . wfGetAllCallers( 5 )
- );
+ 'SessionBackend "{session}" data dirty due to dirty(): {callers}',
+ array(
+ 'session' => $this->id,
+ 'callers' => wfGetAllCallers( 5 ),
+ ) );
}
/**
if ( time() + $this->lifetime / 2 > $this->expires ) {
$this->metaDirty = true;
$this->logger->debug(
- "SessionBackend $this->id metadata dirty for renew(): " . wfGetAllCallers( 5 )
- );
+ 'SessionBackend "{callers}" metadata dirty for renew(): {callers}',
+ array(
+ 'session' => $this->id,
+ 'callers' => wfGetAllCallers( 5 ),
+ ) );
if ( $this->persist ) {
$this->forcePersist = true;
$this->logger->debug(
- "SessionBackend $this->id force-persist for renew(): " . wfGetAllCallers( 5 )
- );
+ 'SessionBackend "{session}" force-persist for renew(): {callers}',
+ array(
+ 'session' => $this->id,
+ 'callers' => wfGetAllCallers( 5 ),
+ ) );
}
}
$this->autosave();
public function save( $closing = false ) {
if ( $this->provider->getManager()->isUserSessionPrevented( $this->user->getName() ) ) {
$this->logger->debug(
- "SessionBackend $this->id not saving, " .
- "user {$this->user} was passed to SessionManager::preventSessionsForUser"
- );
+ 'SessionBackend "{session}" not saving, user {user} was ' .
+ 'passed to SessionManager::preventSessionsForUser',
+ array(
+ 'session' => $this->id,
+ 'user' => $this->user,
+ ) );
return;
}
$anon = $this->user->isAnon();
if ( !$anon && !$this->user->getToken( false ) ) {
$this->logger->debug(
- "SessionBackend $this->id creating token for user {$this->user} on save"
- );
+ 'SessionBackend "{session}" creating token for user {user} on save',
+ array(
+ 'session' => $this->id,
+ 'user' => $this->user,
+ ) );
$this->user->setToken();
if ( !wfReadOnly() ) {
$this->user->saveSettings();
if ( !$this->metaDirty && !$this->dataDirty &&
$this->dataHash !== md5( serialize( $this->data ) )
) {
- $this->logger->debug( "SessionBackend $this->id data dirty due to hash mismatch, " .
- "$this->dataHash !== " . md5( serialize( $this->data ) ) );
+ $this->logger->debug(
+ 'SessionBackend "{session}" data dirty due to hash mismatch, {expected} !== {got}',
+ array(
+ 'session' => $this->id,
+ 'expected' => $this->dataHash,
+ 'got' => md5( serialize( $this->data ) ),
+ ) );
$this->dataDirty = true;
}
return;
}
- $this->logger->debug( "SessionBackend $this->id save: " .
- 'dataDirty=' . (int)$this->dataDirty . ' ' .
- 'metaDirty=' . (int)$this->metaDirty . ' ' .
- 'forcePersist=' . (int)$this->forcePersist
- );
+ $this->logger->debug(
+ 'SessionBackend "{session}" save: dataDirty={dataDirty} ' .
+ 'metaDirty={metaDirty} forcePersist={forcePersist}',
+ array(
+ 'session' => $this->id,
+ 'dataDirty' => (int)$this->dataDirty,
+ 'metaDirty' => (int)$this->metaDirty,
+ 'forcePersist' => (int)$this->forcePersist,
+ ) );
// Persist to the provider, if flagged
if ( $this->persist && ( $this->metaDirty || $this->forcePersist ) ) {
if ( $this->usePhpSessionHandling && session_id() === '' && PHPSessionHandler::isEnabled() &&
SessionManager::getGlobalSession()->getId() === (string)$this->id
) {
- $this->logger->debug( "SessionBackend $this->id: Taking over PHP session" );
+ $this->logger->debug(
+ 'SessionBackend "{session}" Taking over PHP session',
+ array(
+ 'session' => $this->id,
+ ) );
session_id( (string)$this->id );
\MediaWiki\quietCall( 'session_start' );
}
try {
$session = $this->getEmptySessionInternal( $request, $id );
} catch ( \Exception $ex ) {
- $this->logger->error( __METHOD__ . ': failed to create empty session: ' .
- $ex->getMessage() );
+ $this->logger->error( 'Failed to create empty session: {exception}',
+ array(
+ 'method' => __METHOD__,
+ 'exception' => $ex,
+ ) );
$session = null;
}
}
// Checks passed, create the user...
$from = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : 'CLI';
- $logger->info( __METHOD__ . ": creating new user ($userName) - from: $from" );
+ $logger->info( __METHOD__ . ': creating new user ({username}) - from: {url}',
+ array(
+ 'username' => $userName,
+ 'url' => $from,
+ ) );
try {
// Insert the user into the local DB master
$status = $user->addToDatabase();
if ( !$status->isOK() ) {
// @codeCoverageIgnoreStart
- $logger->error( __METHOD__ . ': failed with message ' . $status->getWikiText() );
+ $logger->error( __METHOD__ . ': failed with message ' . $status->getWikiText(),
+ array(
+ 'username' => $userName,
+ ) );
$user->setId( 0 );
$user->loadFromId();
return false;
}
} catch ( \Exception $ex ) {
// @codeCoverageIgnoreStart
- $logger->error( __METHOD__ . ': failed with exception ' . $ex->getMessage() );
+ $logger->error( __METHOD__ . ': failed with exception {exception}', array(
+ 'exception' => $ex,
+ 'username' => $userName,
+ ) );
// Do not keep throwing errors for a while
$cache->set( $backoffKey, 1, 600 );
// Bubble up error; which should normally trigger DB rollbacks
public function preventSessionsForUser( $username ) {
$this->preventUsers[$username] = true;
- // Reset the user's token to kill existing sessions
- $user = User::newFromName( $username );
- if ( $user && $user->getToken( false ) ) {
- $user->setToken();
- $user->saveSettings();
- }
-
// Instruct the session providers to kill any other sessions too.
foreach ( $this->getProviders() as $provider ) {
$provider->preventSessionsForUser( $username );
if ( $blob !== false ) {
// Sanity check: blob must be an array, if it's saved at all
if ( !is_array( $blob ) ) {
- $this->logger->warning( "Session $info: Bad data" );
+ $this->logger->warning( 'Session "{session}": Bad data', array(
+ 'session' => $info,
+ ) );
$this->store->delete( $key );
return false;
}
if ( !isset( $blob['data'] ) || !is_array( $blob['data'] ) ||
!isset( $blob['metadata'] ) || !is_array( $blob['metadata'] )
) {
- $this->logger->warning( "Session $info: Bad data structure" );
+ $this->logger->warning( 'Session "{session}": Bad data structure', array(
+ 'session' => $info,
+ ) );
$this->store->delete( $key );
return false;
}
!array_key_exists( 'userToken', $metadata ) ||
!array_key_exists( 'provider', $metadata )
) {
- $this->logger->warning( "Session $info: Bad metadata" );
+ $this->logger->warning( 'Session "{session}": Bad metadata', array(
+ 'session' => $info,
+ ) );
$this->store->delete( $key );
return false;
}
if ( $provider === null ) {
$newParams['provider'] = $provider = $this->getProvider( $metadata['provider'] );
if ( !$provider ) {
- $this->logger->warning( "Session $info: Unknown provider, " . $metadata['provider'] );
+ $this->logger->warning(
+ 'Session "{session}": Unknown provider ' . $metadata['provider'],
+ array(
+ 'session' => $info,
+ )
+ );
$this->store->delete( $key );
return false;
}
} elseif ( $metadata['provider'] !== (string)$provider ) {
- $this->logger->warning( "Session $info: Wrong provider, " .
- $metadata['provider'] . ' !== ' . $provider );
+ $this->logger->warning( 'Session "{session}": Wrong provider ' .
+ $metadata['provider'] . ' !== ' . $provider,
+ array(
+ 'session' => $info,
+ ) );
return false;
}
$newParams['metadata'] = $newProviderMetadata;
}
} catch ( \UnexpectedValueException $ex ) {
- $this->logger->warning( "Session $info: Metadata merge failed: " . $ex->getMessage() );
+ $this->logger->warning(
+ 'Session "{session}": Metadata merge failed: {exception}',
+ array(
+ 'session' => $info,
+ 'exception' => $ex,
+ ) );
return false;
}
}
$userInfo = UserInfo::newAnonymous();
}
} catch ( \InvalidArgumentException $ex ) {
- $this->logger->error( "Session $info: " . $ex->getMessage() );
+ $this->logger->error( 'Session "{session}": {exception}', array(
+ 'session' => $info,
+ 'exception' => $ex,
+ ) );
return false;
}
$newParams['userInfo'] = $userInfo;
// is no saved ID and the names match.
if ( $metadata['userId'] ) {
if ( $metadata['userId'] !== $userInfo->getId() ) {
- $this->logger->warning( "Session $info: User ID mismatch, " .
- $metadata['userId'] . ' !== ' . $userInfo->getId() );
+ $this->logger->warning(
+ 'Session "{session}": User ID mismatch, {uid_a} !== {uid_b}',
+ array(
+ 'session' => $info,
+ 'uid_a' => $metadata['userId'],
+ 'uid_b' => $userInfo->getId(),
+ ) );
return false;
}
if ( $metadata['userName'] !== null &&
$userInfo->getName() !== $metadata['userName']
) {
- $this->logger->warning( "Session $info: User ID matched but name didn't (rename?), " .
- $metadata['userName'] . ' !== ' . $userInfo->getName() );
+ $this->logger->warning(
+ 'Session "{session}": User ID matched but name didn\'t (rename?), {uname_a} !== {uname_b}',
+ array(
+ 'session' => $info,
+ 'uname_a' => $metadata['userName'],
+ 'uname_b' => $userInfo->getName(),
+ ) );
return false;
}
} elseif ( $metadata['userName'] !== null ) { // Shouldn't happen, but just in case
if ( $metadata['userName'] !== $userInfo->getName() ) {
- $this->logger->warning( "Session $info: User name mismatch, " .
- $metadata['userName'] . ' !== ' . $userInfo->getName() );
+ $this->logger->warning(
+ 'Session "{session}": User name mismatch, {uname_a} !== {uname_b}',
+ array(
+ 'session' => $info,
+ 'uname_a' => $metadata['userName'],
+ 'uname_b' => $userInfo->getName(),
+ ) );
return false;
}
} elseif ( !$userInfo->isAnon() ) {
// Metadata specifies an anonymous user, but the passed-in
// user isn't anonymous.
$this->logger->warning(
- "Session $info: Metadata has an anonymous user, " .
- 'but a non-anon user was provided'
- );
+ 'Session "{session}": Metadata has an anonymous user, but a non-anon user was provided',
+ array(
+ 'session' => $info,
+ ) );
return false;
}
}
if ( $metadata['userToken'] !== null &&
$userInfo->getToken() !== $metadata['userToken']
) {
- $this->logger->warning( "Session $info: User token mismatch" );
+ $this->logger->warning( 'Session "{session}": User token mismatch', array(
+ 'session' => $info,
+ ) );
return false;
}
if ( !$userInfo->isVerified() ) {
} else {
// No metadata, so we can't load the provider if one wasn't given.
if ( $info->getProvider() === null ) {
- $this->logger->warning( "Session $info: Null provider and no metadata" );
+ $this->logger->warning(
+ 'Session "{session}": Null provider and no metadata',
+ array(
+ 'session' => $info,
+ ) );
return false;
}
$newParams['userInfo'] = UserInfo::newAnonymous();
} else {
$this->logger->info(
- "Session $info: No user provided and provider cannot set user"
- );
+ 'Session "{session}": No user provided and provider cannot set user',
+ array(
+ 'session' => $info,
+ ) );
return false;
}
} elseif ( !$info->getUserInfo()->isVerified() ) {
$this->logger->warning(
- "Session $info: Unverified user provided and no metadata to auth it"
- );
+ 'Session "{session}": Unverified user provided and no metadata to auth it',
+ array(
+ 'session' => $info,
+ ) );
return false;
}
'SessionCheckInfo',
array( &$reason, $info, $request, $metadata, $data )
) ) {
- $this->logger->warning( "Session $info: $reason" );
+ $this->logger->warning( 'Session "{session}": ' . $reason, array(
+ 'session' => $info,
+ ) );
return false;
}
// Select: All, None, Invert
$links = array();
$links[] = Html::element(
- 'a', array( 'href' => '#', 'id' => 'checkbox-all' ),
+ 'a', array( 'href' => '#', 'class' => 'mw-checkbox-all' ),
$this->msg( 'checkbox-all' )->text()
);
$links[] = Html::element(
- 'a', array( 'href' => '#', 'id' => 'checkbox-none' ),
+ 'a', array( 'href' => '#', 'class' => 'mw-checkbox-none' ),
$this->msg( 'checkbox-none' )->text()
);
$links[] = Html::element(
- 'a', array( 'href' => '#', 'id' => 'checkbox-invert' ),
+ 'a', array( 'href' => '#', 'class' => 'mw-checkbox-invert' ),
$this->msg( 'checkbox-invert' )->text()
);
if ( $targetTitle->getArticleID() == $destTitle->getArticleID() ) {
return false;
}
- # Verify that this timestamp is valid
- # Must be older than the destination page
- $dbw = wfGetDB( DB_MASTER );
- # Get timestamp into DB format
- $this->mTimestamp = $this->mTimestamp ? $dbw->timestamp( $this->mTimestamp ) : '';
- # Max timestamp should be min of destination page
- $maxtimestamp = $dbw->selectField(
- 'revision',
- 'MIN(rev_timestamp)',
- array( 'rev_page' => $this->mDestID ),
- __METHOD__
- );
- # Destination page must exist with revisions
- if ( !$maxtimestamp ) {
- $this->getOutput()->addWikiMsg( 'mergehistory-fail' );
- return false;
- }
- # Get the latest timestamp of the source
- $lasttimestamp = $dbw->selectField(
- array( 'page', 'revision' ),
- 'rev_timestamp',
- array( 'page_id' => $this->mTargetID, 'page_latest = rev_id' ),
- __METHOD__
- );
- # $this->mTimestamp must be older than $maxtimestamp
- if ( $this->mTimestamp >= $maxtimestamp ) {
- $this->getOutput()->addWikiMsg( 'mergehistory-fail' );
+ // MergeHistory object
+ $mh = new MergeHistory( $targetTitle, $destTitle, $this->mTimestamp );
+ // Merge!
+ $mergeStatus = $mh->merge( $this->getUser(), $this->mComment );
+ if ( !$mergeStatus->isOK() ) {
+ // Failed merge
+ $this->getOutput()->addWikiMsg( $mergeStatus->getMessage() );
return false;
}
- # Get the timestamp pivot condition
- if ( $this->mTimestamp ) {
- $timewhere = "rev_timestamp <= {$this->mTimestamp}";
- $timestampLimit = wfTimestamp( TS_MW, $this->mTimestamp );
- } else {
- $timewhere = "rev_timestamp <= {$maxtimestamp}";
- $timestampLimit = wfTimestamp( TS_MW, $lasttimestamp );
- }
- # Check that there are not too many revisions to move
- $limit = 5000; // avoid too much slave lag
- $count = $dbw->selectRowCount( 'revision', '1',
- array( 'rev_page' => $this->mTargetID, $timewhere ),
- __METHOD__,
- array( 'LIMIT' => $limit + 1 )
- );
- if ( $count > $limit ) {
- $this->getOutput()->addWikiMsg( 'mergehistory-fail-toobig' );
-
- return false;
- }
- # Do the moving...
- $dbw->update(
- 'revision',
- array( 'rev_page' => $this->mDestID ),
- array( 'rev_page' => $this->mTargetID, $timewhere ),
- __METHOD__
- );
-
- $count = $dbw->affectedRows();
- # Make the source page a redirect if no revisions are left
- $haveRevisions = $dbw->selectField(
- 'revision',
- 'rev_timestamp',
- array( 'rev_page' => $this->mTargetID ),
- __METHOD__,
- array( 'FOR UPDATE' )
- );
- if ( !$haveRevisions ) {
- if ( $this->mComment ) {
- $comment = $this->msg(
- 'mergehistory-comment',
- $targetTitle->getPrefixedText(),
- $destTitle->getPrefixedText(),
- $this->mComment
- )->inContentLanguage()->text();
- } else {
- $comment = $this->msg(
- 'mergehistory-autocomment',
- $targetTitle->getPrefixedText(),
- $destTitle->getPrefixedText()
- )->inContentLanguage()->text();
- }
-
- $contentHandler = ContentHandler::getForTitle( $targetTitle );
- $redirectContent = $contentHandler->makeRedirectContent( $destTitle );
-
- if ( $redirectContent ) {
- $redirectPage = WikiPage::factory( $targetTitle );
- $redirectRevision = new Revision( array(
- 'title' => $targetTitle,
- 'page' => $this->mTargetID,
- 'comment' => $comment,
- 'content' => $redirectContent ) );
- $redirectRevision->insertOn( $dbw );
- $redirectPage->updateRevisionOn( $dbw, $redirectRevision );
-
- # Now, we record the link from the redirect to the new title.
- # It should have no other outgoing links...
- $dbw->delete( 'pagelinks', array( 'pl_from' => $this->mDestID ), __METHOD__ );
- $dbw->insert( 'pagelinks',
- array(
- 'pl_from' => $this->mDestID,
- 'pl_from_namespace' => $destTitle->getNamespace(),
- 'pl_namespace' => $destTitle->getNamespace(),
- 'pl_title' => $destTitle->getDBkey() ),
- __METHOD__
- );
- } else {
- // would be nice to show a warning if we couldn't create a redirect
- }
- } else {
- $targetTitle->invalidateCache(); // update histories
- }
- $destTitle->invalidateCache(); // update histories
- # Check if this did anything
- if ( !$count ) {
- $this->getOutput()->addWikiMsg( 'mergehistory-fail' );
-
- return false;
- }
- # Update our logs
- $logEntry = new ManualLogEntry( 'merge', 'merge' );
- $logEntry->setPerformer( $this->getUser() );
- $logEntry->setComment( $this->mComment );
- $logEntry->setTarget( $targetTitle );
- $logEntry->setParameters( array(
- '4::dest' => $destTitle->getPrefixedText(),
- '5::mergepoint' => $timestampLimit
- ) );
- $logId = $logEntry->insert();
- $logEntry->publish( $logId );
$targetLink = Linker::link(
$targetTitle,
$this->getOutput()->addWikiMsg( $this->msg( 'mergehistory-done' )
->rawParams( $targetLink )
->params( $destTitle->getPrefixedText() )
- ->numParams( $count )
+ ->numParams( $mh->getMergedRevisionCount() )
);
- Hooks::run( 'ArticleMergeComplete', array( $targetTitle, $destTitle ) );
-
return true;
}
* @ingroup SpecialPage
*/
class SpecialUndelete extends SpecialPage {
- private $mAction;
+ private $mAction;
private $mTarget;
private $mTimestamp;
private $mRestore;
+ private $mRevdel;
private $mInvert;
private $mFilename;
- private $mTargetTimestamp;
+ private $mTargetTimestamp;
private $mAllowed;
private $mCanView;
private $mComment;
$posted = $request->wasPosted() &&
$user->matchEditToken( $request->getVal( 'wpEditToken' ) );
$this->mRestore = $request->getCheck( 'restore' ) && $posted;
+ $this->mRevdel = $request->getCheck( 'revdel' ) && $posted;
$this->mInvert = $request->getCheck( 'invert' ) && $posted;
$this->mPreview = $request->getCheck( 'preview' ) && $posted;
$this->mDiff = $request->getCheck( 'diff' );
} else {
$this->showFile( $this->mFilename );
}
- } elseif ( $this->mRestore && $this->mAction == 'submit' ) {
- $this->undelete();
+ } elseif ( $this->mAction === "submit" ) {
+ if ( $this->mRestore ) {
+ $this->undelete();
+ } elseif ( $this->mRevdel ) {
+ $this->redirectToRevDel();
+ }
+
} else {
$this->showHistory();
}
}
+ /**
+ * Convert submitted form data to format expected by RevisionDelete and
+ * redirect the request
+ */
+ private function redirectToRevDel() {
+ $archive = new PageArchive( $this->mTargetObj );
+
+ $revisions = array();
+
+ foreach ( $this->getRequest()->getValues() as $key => $val ) {
+ $matches = array();
+ if ( preg_match( "/^ts(\d{14})$/", $key, $matches ) ) {
+ $revisions[ $archive->getRevision( $matches[1] )->getId() ] = 1;
+ }
+ }
+ $query = array(
+ "type" => "revision",
+ "ids" => $revisions,
+ "target" => wfUrlencode( $this->mTargetObj->getPrefixedText() )
+ );
+ $url = SpecialPage::getTitleFor( "RevisionDelete" )->getFullURL( $query );
+ $this->getOutput()->redirect( $url );
+ }
+
function showSearchForm() {
$out = $this->getOutput();
$out->setPageTitle( $this->msg( 'undelete-search-title' ) );
$out->addHTML( Xml::element( 'h2', null, $this->msg( 'history' )->text() ) . "\n" );
if ( $haveRevisions ) {
- # The page's stored (deleted) history:
+ # Show the page's stored (deleted) history
+
+ if ( $this->getUser()->isAllowed( 'deleterevision' ) ) {
+ $out->addHTML( Html::element(
+ 'button',
+ array(
+ 'name' => 'revdel',
+ 'type' => 'submit',
+ 'class' => 'deleterevision-log-submit mw-log-deleterevision-button'
+ ),
+ $this->msg( 'showhideselectedversions' )->text()
+ ) . "\n" );
+ }
+
$out->addHTML( '<ul>' );
$remaining = $revisions->numRows();
$earliestLiveTime = $this->mTargetObj->getEarliestRevTime();
$attribs['class'] = implode( ' ', $classes );
}
- // Revision delete links
- $revdlink = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj );
-
- $revisionRow = $this->msg( 'undelete-revision-row' )
+ $revisionRow = $this->msg( 'undelete-revision-row2' )
->rawParams(
$checkBox,
- $revdlink,
$last,
$pageLink,
$userLink,
*/
const TOKEN_LENGTH = 32;
+ /**
+ * @const string An invalid value for user_token
+ */
+ const INVALID_TOKEN = '*** INVALID ***';
+
/**
* Global constant made accessible as class constants so that autoloader
* magic can be used.
$user = self::newFromRow( $row );
// A user is considered to exist as a non-system user if it has a
- // password set, or a temporary password set, or an email set.
+ // password set, or a temporary password set, or an email set, or a
+ // non-invalid token.
$passwordFactory = new PasswordFactory();
$passwordFactory->init( RequestContext::getMain()->getConfig() );
try {
$newpassword = PasswordFactory::newInvalidPassword();
}
if ( !$password instanceof InvalidPassword || !$newpassword instanceof InvalidPassword
- || $user->mEmail
+ || $user->mEmail || $user->mToken !== self::INVALID_TOKEN
) {
// User exists. Steal it?
if ( !$options['steal'] ) {
__METHOD__
);
$user->invalidateEmail();
+ $user->mToken = self::INVALID_TOKEN;
$user->saveSettings();
+ SessionManager::singleton()->preventSessionsForUser( $user->getName() );
}
- SessionManager::singleton()->preventSessionsForUser( $user->getName() );
-
return $user;
}
// ip-based limits
if ( isset( $limits['ip'] ) ) {
$ip = $this->getRequest()->getIP();
- $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
+ $keys[wfMemcKey( 'limiter', $action, 'ip', $ip )] = $limits['ip'];
}
// subnet-based limits
if ( isset( $limits['subnet'] ) ) {
$ip = $this->getRequest()->getIP();
$subnet = IP::getSubnet( $ip );
if ( $subnet !== false ) {
- $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
+ $keys[wfMemcKey( 'limiter', $action, 'subnet', $subnet )] = $limits['subnet'];
}
}
}
$this->setToken();
}
- // If the user doesn't have a token, return null to indicate that.
- // Otherwise, hmac the version with the secret if we have a version.
if ( !$this->mToken ) {
+ // The user doesn't have a token, return null to indicate that.
return null;
+ } elseif ( $this->mToken === self::INVALID_TOKEN ) {
+ // We return a random value here so existing token checks are very
+ // likely to fail.
+ return MWCryptRand::generateHex( self::TOKEN_LENGTH );
} elseif ( $wgAuthenticationTokenVersion === null ) {
+ // $wgAuthenticationTokenVersion not in use, so return the raw secret
return $this->mToken;
} else {
+ // $wgAuthenticationTokenVersion in use, so hmac it.
$ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
// The raw hash can be overly long. Shorten it up.
*/
public function setToken( $token = false ) {
$this->load();
- if ( !$token ) {
+ if ( $this->mToken === self::INVALID_TOKEN ) {
+ \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
+ ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
+ } elseif ( !$token ) {
$this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
} else {
$this->mToken = $token;
'gl' => 'galego', # Galician
'glk' => 'گیلکی', # Gilaki
'gn' => 'Avañe\'ẽ', # Guaraní, Paraguayan
- 'gom' => 'à¤\97à¥\8bवा à¤\95à¥\8bà¤\82à¤\95णà¥\80 / Gova Konknni', # Goan Konkani
- 'gom-deva' => 'à¤\97à¥\8bवा à¤\95à¥\8bà¤\82à¤\95णà¥\80', # Goan Konkani (Devanagari script)
- 'gom-latn' => 'Gova Konknni', # Goan Konkani (Latin script)
+ 'gom' => 'à¤\97à¥\8bà¤\82यà¤\9aà¥\80 à¤\95à¥\8bà¤\82à¤\95णà¥\80 / Gõychi Konknni', # Goan Konkani
+ 'gom-deva' => 'à¤\97à¥\8bà¤\82यà¤\9aà¥\80 à¤\95à¥\8bà¤\82à¤\95णà¥\80', # Goan Konkani (Devanagari script)
+ 'gom-latn' => 'Gõychi Konknni', # Goan Konkani (Latin script)
'got' => '𐌲𐌿𐍄𐌹𐍃𐌺', # Gothic
'grc' => 'Ἀρχαία ἑλληνικὴ', # Ancient Greek
'gsw' => 'Alemannisch', # Alemannic
"apisandbox-reset": "إفراغ",
"apisandbox-retry": "أعد المحاولة",
"apisandbox-examples": "أمثلة",
- "apisandbox-results": "النتيجة",
+ "apisandbox-results": "النتائج",
"apisandbox-request-url-label": "مسار الطلب:",
"apisandbox-request-time": "وقت الطلب: $1",
"booksources": "مصادر كتاب",
"subject": "قونو:",
"minoredit": "بو بیر کیچیک دَییشدیرمهدیر",
"watchthis": "بو صفحهنی ایزله",
- "savearticle": "صÙ\81ØÙ\87â\80\8cÙ\86Û\8c ساخÙ\84ا",
+ "savearticle": "صÙ\81ØÙ\87â\80\8cÙ\86Û\8c ذخÛ\8cرÙ\87 ائت",
"preview": "اؤنگؤستریش",
"showpreview": "سیناق گؤستریش",
"showdiff": "دَییشیکلیکلری گؤستر",
"botpasswords-created-body": "Пароль робата «$1» быў пасьпяхова створаны.",
"botpasswords-updated-title": "Пароль робата абноўлены",
"botpasswords-updated-body": "Пароль робата «$1» быў пасьпяхова абноўлены.",
+ "botpasswords-deleted-title": "Пароль робата выдалены",
+ "botpasswords-deleted-body": "Пароль робата «$1» быў выдалены.",
"resetpass_forbidden": "Пароль ня можа быць зьменены",
"resetpass-no-info": "Для непасрэднага доступу да гэтай старонкі Вам неабходна ўвайсьці ў сыстэму.",
"resetpass-submit-loggedin": "Зьмяніць пароль",
"apisandbox-examples": "Прыклады",
"apisandbox-results": "Вынікі",
"apisandbox-request-url-label": "URL-адрас запыту:",
- "apisandbox-request-time": "ЧаÑ\81 апÑ\80аÑ\86оÑ\9eкÑ\96 запÑ\8bÑ\82Ñ\83: $1",
+ "apisandbox-request-time": "ЧаÑ\81 запÑ\8bÑ\82Ñ\83: {{PLURAL:$1|$1 мÑ\81}}",
"booksources": "Крыніцы кніг",
"booksources-search-legend": "Пошук кніг",
"booksources-isbn": "ISBN:",
"faqpage": "Project:Чѧстꙑ въпроси",
"actions": "дѣиства",
"namespaces": "имєнъ просторꙑ",
+ "variants": "обраꙁи",
"navigation-heading": "плаваниѥ",
"errorpagetitle": "блаꙁна",
"returnto": "къ страници ⁖ $1 ⁖ въꙁвращєниѥ ⁙",
"ncategories": "$1 {{PLURAL:$1|катигорїꙗ|катигорїи|катигорїѩ}}",
"nlinks": "$1 {{PLURAL:$1|съвѧꙁь|съвѧꙁи|съвѧꙁии}}",
"nmembers": "$1 {{PLURAL:$1|члѣнъ|члѣна|члѣни|члѣнъ}}",
+ "uncategorizedpages": "бєскатигорїинꙑ страницѧ",
+ "uncategorizedcategories": "бєскатигорїинꙑ катигорїѩ",
+ "uncategorizedimages": "бєскатигорїинꙑ дѣла",
+ "uncategorizedtemplates": "бєскатигорїинꙑ обраꙁьци",
+ "wantedcategories": "ноуждьни катигорїѩ",
+ "wantedpages": "ноуждьни страницѧ",
+ "wantedfiles": "ноуждьни дѣла",
+ "wantedtemplates": "ноуждьни обраꙁьци",
"shortpages": "кратъкꙑ страницѧ",
"longpages": "дльгꙑ страницѧ",
"protectedpages-reason": "какъ съмꙑслъ",
"previewnote": "<strong>Remember that this is only a preview.</strong>\nYour changes have not yet been saved!",
"continue-editing": "Go to editing area",
"previewconflict": "This preview reflects the text in the upper text editing area as it will appear if you choose to save.",
- "session_fail_preview": "<strong>Sorry! We could not process your edit due to a loss of session data.</strong>\nPlease try again.\nIf it still does not work, try [[Special:UserLogout|logging out]] and logging back in.",
- "session_fail_preview_html": "<strong>Sorry! We could not process your edit due to a loss of session data.</strong>\n\n<em>Because {{SITENAME}} has raw HTML enabled, the preview is hidden as a precaution against JavaScript attacks.</em>\n\n<strong>If this is a legitimate edit attempt, please try again.</strong>\nIf it still does not work, try [[Special:UserLogout|logging out]] and logging back in.",
+ "session_fail_preview": "Sorry! We could not process your edit due to a loss of session data.\n\nYou might have been logged out. <strong>Please verify that you're still logged in and try again</strong>.\nIf it still does not work, try [[Special:UserLogout|logging out]] and logging back in, and check that your browser allows cookies from this site.",
+ "session_fail_preview_html": "Sorry! We could not process your edit due to a loss of session data.\n\n<em>Because {{SITENAME}} has raw HTML enabled, the preview is hidden as a precaution against JavaScript attacks.</em>\n\n<strong>If this is a legitimate edit attempt, please try again.</strong>\nIf it still does not work, try [[Special:UserLogout|logging out]] and logging back in, and check that your browser allows cookies from this site.",
"token_suffix_mismatch": "<strong>Your edit has been rejected because your client mangled the punctuation characters in the edit token.</strong>\nThe edit has been rejected to prevent corruption of the page text.\nThis sometimes happens when you are using a buggy web-based anonymous proxy service.",
"edit_form_incomplete": "<strong>Some parts of the edit form did not reach the server; double-check that your edits are intact and try again.</strong>",
"editing": "Editing $1",
"mergehistory-empty": "No revisions can be merged.",
"mergehistory-done": "$3 {{PLURAL:$3|revision|revisions}} of $1 {{PLURAL:$3|was|were}} merged into [[:$2]].",
"mergehistory-fail": "Unable to perform history merge, please recheck the page and time parameters.",
- "mergehistory-fail-toobig" : "Unable to perform history merge as more than the limit of $1 {{PLURAL:$1|revision|revisions}} would be moved.",
+ "mergehistory-fail-bad-timestamp": "Timestamp is invalid.",
+ "mergehistory-fail-invalid-source": "Source page is invalid.",
+ "mergehistory-fail-invalid-dest": "Destination page is invalid.",
+ "mergehistory-fail-no-change": "History merge did not merge any revisions. Please recheck the page and time parameters.",
+ "mergehistory-fail-permission": "Insufficient permissions to merge history.",
+ "mergehistory-fail-self-merge": "Source and destination pages are the same.",
+ "mergehistory-fail-timestamps-overlap": "Source revisions overlap or come after destination revisions.",
+ "mergehistory-fail-toobig": "Unable to perform history merge as more than the limit of $1 {{PLURAL:$1|revision|revisions}} would be moved.",
+ "mergehistory-warning-redirect-not-created": "",
"mergehistory-no-source": "Source page $1 does not exist.",
"mergehistory-no-destination": "Destination page $1 does not exist.",
"mergehistory-invalid-source": "Source page must be a valid title.",
"mergehistory-same-destination": "Source and destination pages cannot be the same",
"mergehistory-reason": "Reason:",
"mergehistory-revisionrow": "$1 ($2) $3 . . $4 $5 $6",
+ "mergehistory-redirect-text": "",
"mergelog": "Merge log",
"pagemerge-logentry": "merged [[$1]] into [[$2]] (revisions up to $3)",
"revertmerge": "Unmerge",
"undelete-error-long": "Errors were encountered while undeleting the file:\n\n$1",
"undelete-show-file-confirm": "Are you sure you want to view the deleted revision of the file \"<nowiki>$1</nowiki>\" from $2 at $3?",
"undelete-show-file-submit": "Yes",
- "undelete-revision-row": "$1 $2 ($3) $4 . . $5 $6 $7 $8 $9",
+ "undelete-revision-row2": "$1 ($2) $3 . . $4 $5 $6 $7 $8",
"namespace": "Namespace:",
"invert": "Invert selection",
"tooltip-invert": "Check this box to hide changes to pages within the selected namespace (and the associated namespace if checked)",
"import-nonewrevisions": "No revisions imported (all were either already present, or skipped due to errors).",
"xml-error-string": "$1 at line $2, col $3 (byte $4): $5",
"import-upload": "Upload XML data",
- "import-token-mismatch": "Loss of session data.\nPlease try again.",
+ "import-token-mismatch": "Loss of session data.\n\nYou might have been logged out. <strong>Please verify that you're still logged in and try again</strong>.\nIf it still does not work, try [[Special:UserLogout|logging out]] and logging back in, and check that your browser allows cookies from this site.",
"import-invalid-interwiki": "Cannot import from the specified wiki.",
"import-error-edit": "Page \"$1\" was not imported because you are not allowed to edit it.",
"import-error-create": "Page \"$1\" was not imported because you are not allowed to create it.",
"expand_templates_generate_xml": "Show XML parse tree",
"expand_templates_generate_rawhtml": "Show raw HTML",
"expand_templates_preview": "Preview",
- "expand_templates_preview_fail_html": "<em>Because {{SITENAME}} has raw HTML enabled and there was a loss of session data, the preview is hidden as a precaution against JavaScript attacks.</em>\n\n<strong>If this is a legitimate preview attempt, please try again.</strong>\nIf it still does not work, try [[Special:UserLogout|logging out]] and logging back in.",
+ "expand_templates_preview_fail_html": "<em>Because {{SITENAME}} has raw HTML enabled and there was a loss of session data, the preview is hidden as a precaution against JavaScript attacks.</em>\n\n<strong>If this is a legitimate preview attempt, please try again.</strong>\nIf it still does not work, try [[Special:UserLogout|logging out]] and logging back in, and check that your browser allows cookies from this site.",
"expand_templates_preview_fail_html_anon": "<em>Because {{SITENAME}} has raw HTML enabled and you are not logged in, the preview is hidden as a precaution against JavaScript attacks.</em>\n\n<strong>If this is a legitimate preview attempt, please [[Special:UserLogin|log in]] and try again.</strong>",
"expand_templates_input_missing": "You need to provide at least some input text.",
"pagelanguage": "Change page language",
"Cindie.Capel",
"ElGatoSaez",
"Joaquin1001",
- "YoViajo"
+ "YoViajo",
+ "Asierog"
]
},
"tog-underline": "Subrayar los enlaces:",
"rcshowhidemine": "$1 mis ediciones",
"rcshowhidemine-show": "Mostrar",
"rcshowhidemine-hide": "Ocultar",
- "rcshowhidecategorization": "$1 categorización de página",
+ "rcshowhidecategorization": "$1 categorización de páginas",
"rcshowhidecategorization-show": "Mostrar",
"rcshowhidecategorization-hide": "Ocultar",
"rclinks": "Ver los últimos $1 cambios en los últimos $2 días.<br />$3",
"apisandbox-retry": "Reintentar",
"apisandbox-no-parameters": "Este módulo API no tiene parámetros.",
"apisandbox-helpurls": "Enlaces de ayuda",
- "apisandbox-examples": "Ejemplo",
+ "apisandbox-examples": "Ejemplos",
"apisandbox-dynamic-parameters": "Parámetros adicionales",
"apisandbox-dynamic-parameters-add-label": "Añadir parámetro:",
"apisandbox-dynamic-parameters-add-placeholder": "Nombre del parámetro",
"apisandbox-submit-invalid-fields-title": "Algunos campos no son válidos",
"apisandbox-results": "Resultados",
"apisandbox-sending-request": "Enviando pedido API...",
+ "apisandbox-loading-results": "Recibiendo resultados API...",
"apisandbox-request-url-label": "URL solicitante:",
"apisandbox-request-time": "Tiempo de solicitud: $1",
"booksources": "Fuentes de libros",
"listgrouprights-namespaceprotection-restrictedto": "Derechos de usuario para editar",
"listgrants": "Subvenciones",
"listgrants-grant": "Conceder",
- "listgrants-rights": "Conceder",
+ "listgrants-rights": "Derechos",
"trackingcategories": "Categorías de seguimiento",
"trackingcategories-summary": "Esta página lista categorías de seguimiento que han sido generadas automáticamente por el software MediaWiki. Sus nombres pueden cambiarse editando su mensaje correspondiente en el espacio de nombres {{ns:8}}.",
"trackingcategories-msg": "Categoría de seguimiento",
"wlshowhideanons": "usuarios anónimos",
"wlshowhidepatr": "ediciones verificadas",
"wlshowhidemine": "mis ediciones",
- "wlshowhidecategorization": "categorización de página",
+ "wlshowhidecategorization": "categorización de páginas",
"watchlist-options": "Opciones de la lista de seguimiento",
"watching": "Vigilando...",
"unwatching": "Eliminando de la lista de seguimiento...",
"Arkaitz Barnetik",
"Sator",
"Macofe",
- "Xð"
+ "Xð",
+ "Asierog"
]
},
"tog-underline": "Azpimarratu loturak:",
"resetpass_submit": "Pasahitza definitu eta saioa hasi",
"changepassword-success": "Zure pasahitza ondo aldatu da!",
"changepassword-throttled": "Saioa hasteko saiakera gehiegi egin berri dituzu.\nBerriro saiatu aurretik $1 itxoin, mesedez.",
+ "botpasswords-label-create": "Sortu",
+ "botpasswords-label-update": "Eguneratu",
+ "botpasswords-label-delete": "Ezabatu",
"resetpass_forbidden": "Ezin dira pasahitzak aldatu",
"resetpass-no-info": "Orrialde honetara zuzenean sartzeko izena eman behar duzu.",
"resetpass-submit-loggedin": "Pasahitza aldatu",
"userrights": "Erabiltzaile baimenen kudeaketa",
"userrights-lookup-user": "Erabiltzaile taldeak kudeatu",
"userrights-user-editname": "Erabiltzaile izena idatzi:",
- "editusergroup": "Erabiltzaile taldeak editatu",
+ "editusergroup": "{{GENDER:$1|Erabiltzaile}} taldeak editatu",
"editinguser": "'''[[User:$1|$1]]''' $2 lankidearen erabiltzaile-eskubideak aldatzen",
"userrights-editusergroup": "Erabiltzaile taldeak editatu",
"saveusergroups": "Erabiltzaile taldeak gorde",
"querypage-disabled": "Orrialde berezi hau desgaituta dago funtzionamendu arrazoiengatik.",
"apihelp": "API laguntza",
"apihelp-no-such-module": "Ez da \"$1\" modulua aurkitu.",
+ "apisandbox": "API proba orria",
+ "apisandbox-submit": "Egin eskaera",
+ "apisandbox-reset": "Garbitu",
+ "apisandbox-examples": "Adibideak",
+ "apisandbox-results": "Emaitzak",
"booksources": "Iturri liburuak",
"booksources-search-legend": "Liburuen bilaketa",
"booksources-search": "Bilatu",
"activeusers-noresult": "Ez da lankiderik aurkitu.",
"listgrouprights": "Erabiltzaile talde eskumenak",
"listgrouprights-summary": "Ondorengo zerrendak wikian dauden lankide taldeak agertzen dira, beraien eskubideekin.\nBadago [[{{MediaWiki:Listgrouprights-helppage}}|informazio osagarria]] banakako eskubideei buruz.",
- "listgrouprights-key": "* <span class=\"listgrouprights-granted\">Eskubidea emanda</span>\n* <span class=\"listgrouprights-revoked\">Eskubidea kenduta</span>",
+ "listgrouprights-key": "Legenda\n* <span class=\"listgrouprights-granted\">Eskubidea emanda</span>\n* <span class=\"listgrouprights-revoked\">Eskubidea kenduta</span>",
"listgrouprights-group": "Taldea",
"listgrouprights-rights": "Eskumenak",
"listgrouprights-helppage": "Help:Talde eskumenak",
"wlshowlast": "Erakutsi azken $1 orduak, azken $2 egunak",
"watchlist-hide": "Ezkutatu",
"watchlist-submit": "Erakutsi",
- "wlshowtime": "Erakutsi azkenak:",
+ "wlshowtime": "Erakusteko denboraldia:",
"wlshowhideminor": "aldaketa txikiak",
"wlshowhidebots": "bot-ak",
"wlshowhideliu": "Erregistratutako erabiltzaileak",
"Macofe",
"Danialbehzadi",
"MRG90",
- "Mahdy Saffar"
+ "Mahdy Saffar",
+ "Arian Ar"
]
},
"tog-underline": "خط کشیدن زیر پیوندها:",
"tog-enotifwatchlistpages": "اگر صفحه یا پروندهای از فهرست پیگیریهایم ویرایش شد به من ایمیلی فرستاده شود",
"tog-enotifusertalkpages": "هنگامی که در صفحهٔ بحث کاربریام تغییری صورت میگیرد به من ایمیلی فرستاده شود",
"tog-enotifminoredits": "برای تغییرات جزئی در صفحهها و پروندهها هم به من ایمیلی فرستاده شود",
- "tog-enotifrevealaddr": "Ù\86شاÙ\86Û\8c پست اÙ\84کترÙ\88Ù\86Û\8cÚ©Û\8c Ù\85Ù\86 را در اÛ\8cÙ\85Û\8cÙ\84â\80\8cÙ\87اÛ\8c اطÙ\84اعâ\80\8cرساÙ\86Û\8c Ù\87Ù\88Û\8cدا گردد",
+ "tog-enotifrevealaddr": "نشانی پست الکترونیکی من در ایمیلهای اطلاعرسانی هویدا گردد",
"tog-shownumberswatching": "شمار کاربران پیگیرندهٔ نمایش یابد",
"tog-oldsig": "امضای کنونی:",
"tog-fancysig": "امضا به صورت ویکیمتن در نظر گرفته شود (بدون درج خودکار پیوند)",
"create-this-page": "ایجاد این صفحه",
"delete": "حذف",
"deletethispage": "حذف این صفحه",
- "undeletethispage": "بازگرداÙ\86ی این صفحه",
+ "undeletethispage": "اØÛ\8cای این صفحه",
"undelete_short": "احیای {{PLURAL:$1|یک ویرایش|$1 ویرایش}}",
"viewdeleted_short": "نمایش {{PLURAL:$1|یک ویرایش حذفشده|$1 ویرایش حذفشده}}",
"protect": "محافظت",
"tog-hideminor": "הסתרת שינויים משניים ברשימת השינויים האחרונים",
"tog-hidepatrolled": "הסתרת שינויים בדוקים ברשימת השינויים האחרונים",
"tog-newpageshidepatrolled": "הסתרת דפים בדוקים ברשימת הדפים החדשים",
- "tog-hidecategorization": "×\94סתרת ×\94×\95ספ×\95ת ×\95×\94סר×\95ת ש×\9c ×\93פ×\99×\9d ×\9eקטגוריות",
+ "tog-hidecategorization": "×\94סתרת ס×\99×\95×\95×\92 ×\93פ×\99×\9d ×\9cקטגוריות",
"tog-extendwatchlist": "הרחבת רשימת המעקב כך שתציג את כל השינויים, לא רק את השינויים האחרונים בכל דף",
"tog-usenewrc": "קיבוץ השינויים לפי דף בשינויים האחרונים וברשימת המעקב",
"tog-numberheadings": "מספור כותרות אוטומטי",
"tog-watchlistreloadautomatically": "רענון אוטומטי של רשימת המעקב בכל פעם שמסנן משתנה (נדרש JavaScript)",
"tog-watchlisthideanons": "הסתרת עריכות של משתמשים אנונימיים ברשימת המעקב",
"tog-watchlisthidepatrolled": "הסתרת עריכות בדוקות ברשימת המעקב",
- "tog-watchlisthidecategorization": "×\94סתרת ×\94×\95ספ×\95ת ×\95×\94סר×\95ת ש×\9c ×\93פ×\99×\9d ×\9eקטגוריות",
+ "tog-watchlisthidecategorization": "×\94סתרת ס×\99×\95×\95×\92 ×\93פ×\99×\9d ×\9cקטגוריות",
"tog-ccmeonemails": "לשלוח אליי העתקים של הודעות דואר אלקטרוני ששלחתי למשתמשים אחרים",
"tog-diffonly": "ביטול הצגת תוכן הדף מתחת להשוואות הגרסאות",
"tog-showhiddencats": "הצגת קטגוריות מוסתרות",
"wlshowhideanons": "משתמשים אנונימיים",
"wlshowhidepatr": "עריכות בדוקות",
"wlshowhidemine": "עריכות שלי",
- "wlshowhidecategorization": "×\94×\95ספ×\95ת ×\95×\94סר×\95ת ש×\9c ×\93פ×\99×\9d ×\9eקטגוריות",
+ "wlshowhidecategorization": "ס×\99×\95×\95×\92 ×\93פ×\99×\9d ×\9cקטגוריות",
"watchlist-options": "אפשרויות ברשימת המעקב",
"watching": "בהוספה לרשימת המעקב…",
"unwatching": "בהסרה מרשימת המעקב…",
"preview": "Pêşdîtin",
"showpreview": "Pêşdîtinê nîşan bide",
"showdiff": "Guherandinan nîşan bide",
- "anoneditwarning": "<strong>Hişyarî:<strong> Tu netêketî yî! Navnîşana IP'ya te wê di dîroka guherandina vê rûpelê de bê tomarkirin. Heke tu <strong>[$1 têkevî]</strong>, li gel sûdên te yên din guhertinên ku tu bikî jî wê ji nasnavê te re bê atfkirin.",
+ "anoneditwarning": "<strong>Hişyarî:<strong> Tu netêketî yî! Navnîşana IP'ya te wê di dîroka guherandina vê rûpelê de bê tomarkirin. Heke tu <strong>[$1 têkevî]</strong> an jî <strong>[$2 hesabekî çêbikî]</strong>, li gel sûdên te yên din guhertinên ku tu bikî jî wê ji nasnavê te re bê atfkirin.",
"anonpreviewwarning": "''Tu ne têketî yî. Tomarkirin wê navnîşana IP'ya te di dîroka guhertinan de nîşan bide.''",
"missingsummary": "<span style=\"color:#990000;\">'''Zanibe:'''</span> Te nivîsekî kurt ji bo guherandinê ra nenivîsand. Eger tu niha carekî din li Tomar xê, guherandinê te vê nivîsekî kurt yê were tomarkirin.",
"missingcommenttext": "Ji kerema xwe kurteya naverokê li jêr binivisîne.",
"boteditletter": "b",
"number_of_watching_users_pageview": "[{{PLURAL:$1|bikarhênerek|$1 bikarhêner}} vê rûpelê {{PLURAL:$1|dişopîne|dişopînin}}.]",
"rc_categories_any": "Qet",
+ "rc-change-size-new": "Piştî guhertinê $1 {{PLURAL:$1|bayt}}",
"newsectionsummary": "/* $1 */ beşeke nû",
"rc-enhanced-expand": "Hûragahiyan nîşan bide",
"rc-enhanced-hide": "Kitûmatan veşêre",
"import-upload": "Daneyên XMLê bar bike",
"importlogpage": "Têketina tevlîkirinê",
"javascripttest": "JavaScript tê testkirin",
- "tooltip-pt-userpage": "Rûpela min",
+ "tooltip-pt-userpage": "Rûpela {{GENDER:|Te}}",
"tooltip-pt-anonuserpage": "Rûpela bikarhênerê ji bo navnîşana ÎP ku tu sererast dikî wekî",
- "tooltip-pt-mytalk": "Gotûbêja min",
- "tooltip-pt-preferences": "Hevyazên min",
+ "tooltip-pt-mytalk": "Gotûbêja {{GENDER:|Te}}",
+ "tooltip-pt-preferences": "Tercîhên {{GENDER:|te}}",
"tooltip-pt-watchlist": "The list of pages you",
- "tooltip-pt-mycontris": "Lîsteya beşdariyên min",
+ "tooltip-pt-mycontris": "Lîsteyekê beşdariyên {{GENDER:|te}}",
+ "tooltip-pt-login": "Têketina we tê teşwîqkirin; lêbelê, en ne elzem e",
"tooltip-pt-logout": "Derkeve",
"tooltip-ca-talk": "Gotûbêj li ser rûpela naverokê",
"tooltip-ca-edit": "Vê rûpelê biguherîne",
"tooltip-t-recentchangeslinked": "Recent changes in pages linking to this page",
"tooltip-feed-rss": "RSS feed'ên ji bo rûpelê",
"tooltip-feed-atom": "Atom feed'ên ji bo vê rûpelê",
- "tooltip-t-contributions": "Lîsteya beşdariyên bikarhêner bibîne",
+ "tooltip-t-contributions": "Lîsteyekî beşdariyên {{GENDER:$1|vê bikarhênerê}} bibîne",
"tooltip-t-emailuser": "Jê re name bişîne",
"tooltip-t-info": "Bêhtir agahî di derbarê vê rûpelê de",
"tooltip-t-upload": "Dosyeyan bar bike",
"otherlanguages": "In aliis linguis",
"redirectedfrom": "(Redirectum de $1)",
"redirectpagesub": "Pagina redirectionis",
- "lastmodifiedat": "Novissima mutatio die $2 hora $1 facta.",
+ "lastmodifiedat": "Novissima mutatio die $1 hora $2 facta.",
"viewcount": "Haec pagina iam vista est {{PLURAL:$1|semel|$1 vices}}.",
"protectedpage": "Pagina protecta",
"jumpto": "Salire ad:",
"movenosubpage": "Dës Säit huet keng Ënnersäiten.",
"movereason": "Grond:",
"revertmove": "zréck réckelen",
- "delete_and_move_text": "== Läsche vun der Destinatiounssäit néideg ==\nD'Säit \"[[:$1]]\" existéiert schonn. \nWëll Dir se läsche fir d'Réckelen ze erméiglechen?",
+ "delete_and_move_text": "D'Zilsäit \"[[:$1]]\" gëtt et schonn. \nWëll Dir se läsche fir d'Réckelen ze erméiglechen?",
"delete_and_move_confirm": "Jo, läsch d'Säit",
"delete_and_move_reason": "Geläscht fir Plaz ze maache fir \"[[$1]]\" heihin ze réckelen",
"selfmove": "Source- an Destinatiounsnumm sinn dselwecht; eng Säit kann net op sech selwer geréckelt ginn.",
"searchprofile-images-tooltip": "Søk etter filer",
"searchprofile-everything-tooltip": "Søk i alt innhold (inkldert diskusjonssider)",
"searchprofile-advanced-tooltip": "Søk i visse navnerom",
- "search-result-size": "$1 ({{PLURAL:$2|ett|$2}} ord)",
+ "search-result-size": "$1 ({{PLURAL:$2|ett ord|$2 ord}})",
"search-result-category-size": "{{PLURAL:$1|1 medlem|$1 medlemmer}} ({{PLURAL:$2|1 underkategori|$2 underkategorier}}, {{PLURAL:$3|1 fil|$3 filer}})",
"search-redirect": "(omdirigering $1)",
"search-section": "(avsnitt $1)",
"sp-contributions-toponly": "Vis kun endringer som er gjeldende revisjoner",
"sp-contributions-newonly": "Bare vis bidrag som er sideopprettinger",
"sp-contributions-submit": "Søk",
- "whatlinkshere": "Lenker hit",
+ "whatlinkshere": "Hva lenker hit",
"whatlinkshere-title": "Sider som lenker til «$1»",
"whatlinkshere-page": "Side:",
"linkshere": "Følgende sider lenker til '''[[:$1]]''':",
"tooltip-search-fulltext": "Søk etter sider som innholder denne teksten",
"tooltip-p-logo": "Gå til hovedsiden",
"tooltip-n-mainpage": "Gå til hovedsiden",
- "tooltip-n-mainpage-description": "Gå til hovedsiden",
+ "tooltip-n-mainpage-description": "Besøk hovedsiden",
"tooltip-n-portal": "Om prosjektet, hva du kan gjøre, hvor du kan finne ting",
"tooltip-n-currentevents": "Finn bakgrunnsinformasjon om aktuelle hendelser",
"tooltip-n-recentchanges": "Liste over siste endringer på wikien.",
"metadata-help": "Denne filen inneholder tilleggsinformasjon, antagligvis lagt til av digitalkameraet eller skanneren brukt til å lage eller digitalisere det.\nHvis filen har blitt forandret fra utgangspunktet, kan enkelte detaljer være unøyaktige.",
"metadata-expand": "Vis utvidede detaljer",
"metadata-collapse": "Skjul utvidede detaljer",
- "metadata-fields": "Bildemetadatafelt listet i denne meldingen inkluderes på bildesiden når metadatatabellen er slått sammen.\nAndre vil skjules som standard.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
+ "metadata-fields": "Bildemetadatafelt listet i denne meldingen inkluderes på bildesiden når metadatatabellen har kollapset.\nAndre vil skjules som standard.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
"exif-imagewidth": "Bredde",
"exif-imagelength": "Høyde",
"exif-bitspersample": "Bits per komponent",
"apisandbox-submit": "Fazer o pedido",
"apisandbox-reset": "Limpar",
"apisandbox-examples": "Exemplos",
- "apisandbox-results": "Resultado",
+ "apisandbox-results": "Resultados",
"apisandbox-request-url-label": "URL do pedido:",
"apisandbox-request-time": "Tempo de processamento: $1",
"booksources": "Fontes bibliográficas",
"previewnote": "Note displayed when clicking on Show preview",
"continue-editing": "{{doc-actionlink}}\nA link to the beginning of the editing textarea on the same page.\n\nDisplayed after {{msg-mw|previewnote}}.",
"previewconflict": "Used in Preview page.",
- "session_fail_preview": "Error message in Preview page.\n\nSee also:\n* {{msg-mw|Token suffix mismatch}}\n* {{msg-mw|Session fail preview}}\n* {{msg-mw|Edit form incomplete}}",
- "session_fail_preview_html": "Used as error message in Preview page.",
+ "session_fail_preview": "Error message in Preview page.\n\nSee also:\n* {{msg-mw|Token suffix mismatch}}\n* {{msg-mw|Session fail preview}}\n* {{msg-mw|Edit form incomplete}}\n\n{{Identical|Loss of session data}}",
+ "session_fail_preview_html": "Used as error message in Preview page.\n\n{{Identical|Loss of session data}}",
"token_suffix_mismatch": "Error message in Preview page.\n\nSee also:\n* {{msg-mw|Token suffix mismatch}}\n* {{msg-mw|Session fail preview}}\n* {{msg-mw|Edit form incomplete}}",
"edit_form_incomplete": "Error message in Preview page.\n\nSee also:\n* {{msg-mw|Token suffix mismatch}}\n* {{msg-mw|Session fail preview}}\n* {{msg-mw|Edit form incomplete}}",
"editing": "Shown as page title when editing a page. Parameters:\n* $1 - the name of the page that is being edited. e.g. \"Editing Main Page\"\n{{Related|Editing}}\n{{Identical|Editing}}",
"mergehistory-empty": "Used in [[Special:MergeHistory]].",
"mergehistory-done": "Success message shown on [[Special:MergeHistory]].\n* $1 - link to target page\n* $2 - destination page title\n* $3 - number of revisions which succeeded to merge",
"mergehistory-fail": "Used as error message in [[Special:MergeHistory]].",
- "mergehistory-fail-toobig": "Used as error message in [[Special:MergeHistory]].\n* $1 - maximum allowed number of revisions that can be moved",
+ "mergehistory-fail-bad-timestamp": "Used as error message in [[Special:MergeHistory]] API.",
+ "mergehistory-fail-invalid-source": "Used as error message in [[Special:MergeHistory]] API.",
+ "mergehistory-fail-invalid-dest": "Used as error message in [[Special:MergeHistory]] API.",
+ "mergehistory-fail-no-change": "Used as error message in [[Special:MergeHistory]] API.",
+ "mergehistory-fail-permission": "Used as error message in [[Special:MergeHistory]] API.",
+ "mergehistory-fail-self-merge": "Used as error message in [[Special:MergeHistory]] API.",
+ "mergehistory-fail-timestamps-overlap": "Used as error message in [[Special:MergeHistory]] API.",
+ "mergehistory-fail-toobig": "Used as error message in [[Special:MergeHistory]] and API.\n* $1 - maximum allowed number of revisions that can be moved",
+ "mergehistory-warning-redirect-not-created": "Used as warning message in [[Special:MergeHistory]] API.",
"mergehistory-no-source": "Used as error message in [[Special:MergeHistory]].\n* $1 - source page title\nSee also:\n* {{msg-mw|mergehistory-invalid-source}}\n* {{msg-mw|mergehistory-invalid-destination}}\n* {{msg-mw|mergehistory-no-destination}}\n* {{msg-mw|mergehistory-same-destination}}",
"mergehistory-no-destination": "Used as error message in [[Special:MergeHistory]].\n* $1 - destination page title\nSee also:\n* {{msg-mw|mergehistory-invalid-source}}\n* {{msg-mw|mergehistory-no-source}}\n* {{msg-mw|mergehistory-invalid-destination}}\n* {{msg-mw|mergehistory-same-destination}}",
"mergehistory-invalid-source": "Used as error message in [[Special:MergeHistory]].\n\nSee also:\n* {{msg-mw|mergehistory-no-source}}\n* {{msg-mw|mergehistory-invalid-destination}}\n* {{msg-mw|mergehistory-no-destination}}\n* {{msg-mw|mergehistory-same-destination}}",
"mergehistory-same-destination": "Error message shown on [[Special:MergeHistory]] when the user entered the same page title to both source and destination\n\nSee also:\n* {{msg-mw|mergehistory-invalid-source}}\n* {{msg-mw|mergehistory-no-source}}\n* {{msg-mw|mergehistory-invalid-destination}}\n* {{msg-mw|mergehistory-no-destination}}",
"mergehistory-reason": "{{Identical|Reason}}",
"mergehistory-revisionrow": "{{Optional}}\nA revision row in the merge history page. Parameters:\n* $1 - a radio button to indicate a merge point\n* $2 - a link to the last revision of a page ({{msg-mw|Last}})\n* $3 - a page link\n* $4 - a user link\n* $5 - a revision size\n* $6 - a revision comment",
+ "mergehistory-redirect-text": "{{ignored}}The text that's added to a redirected page when that redirect is created as part of a history merge.",
"mergelog": "{{doc-logpage}}\n\nThis is the name of a log of merge actions done on [[Special:MergeHistory]]. This special page and this log is not enabled by default.",
"pagemerge-logentry": "{{ignored}}This is a ''logentry'' message only used on IRC.\n\nParameters:\n* $1 - the page name of the source of the content to be merged\n* $2 - the page into which the content is merged\n* $3 - a timestamp of limit\n\nThe log and its associated special page 'MergeHistory' is not enabled by default.\n\nPlease note that the parameters in a log entry will appear in the log only in the default language of the wiki. View [[Special:Log]] for examples on translatewiki.net with English default language.",
"revertmerge": "Used as link text",
"undelete-error-long": "Used as error message. Parameters:\n* $1 - ...\nSee also:\n* {{msg-mw|Undelete-error-short}}",
"undelete-show-file-confirm": "A confirmation message shown on [[Special:Undelete]] when the request does not contain a valid token (e.g. when a user clicks a link received in mail).\n\nParameters:\n* $1 - the name of the file being undeleted\n* $2 - the date of the displayed revision\n* $3 - the time of the displayed revision\n{{Identical|Are you sure you want to view the deleted revision of the file...}}",
"undelete-show-file-submit": "{{Identical|Yes}}",
- "undelete-revision-row": "{{Optional}}\nA revision row in the undelete page. Parameters:\n* $1 is a checkBox to indicate whether to restore this specific revision\n* $2 is a link to the revision\n* $3 is a link to the last revision of a page ({{msg-mw|last}})\n* $4 is a link to the page\n* $5 is a link to the revision's user\n* $6 is the revision's minor edit identifier\n* $7 is the revision size\n* $8 is the revision comment\n* $9 is the revision's tags",
+ "undelete-revision-row2": "{{Optional}}\nA revision row in the undelete page. Parameters:\n* $1 is a checkBox to indicate whether to restore this specific revision\n* $2 is a link to the last revision of a page ({{msg-mw|last}})\n* $3 is a link to the page\n* $4 is a link to the revision's user\n* $5 is the revision's minor edit identifier\n* $6 is the revision size\n* $7 is the revision comment\n* $8 is the revision's tags",
"namespace": "This message is located at [[Special:Contributions]].\n{{Identical|Namespace}}",
"invert": "Displayed in [[Special:RecentChanges|RecentChanges]], [[Special:RecentChangesLinked|RecentChangesLinked]] and [[Special:Watchlist|Watchlist]].\n\nThis message means \"Invert selection of namespace\".\n\nThis message has a tooltip {{msg-mw|tooltip-invert}}\n{{Identical|Invert selection}}",
"tooltip-invert": "Used in [[Special:Recentchanges]] as a tooltip for the invert checkbox. See also the message {{msg-mw|invert}}",
"import-nonewrevisions": "Used in [[Special:Import]].",
"xml-error-string": "Parameters:\n* $1 - Some kind of message, perhaps name of the error?\n* $2 - line number\n* $3 - column number\n* $4 - ?? $this->mByte . $this->mContext\n* $5 - error description\nExample:\n* Import failed: XML import parse failure at line 1, col 1 (byte 3; \"- <mediawiki xml\"): Empty document",
"import-upload": "Used on [[Special:Import]].\n\nRelated messages:\n* {{msg-mw|right-importupload}} (the user right for this)",
- "import-token-mismatch": "Used as error message in [[Special:Import]].\n\nSee also:\n* {{msg-mw|import-token-mismatch}}\n* {{msg-mw|import-invalid-interwiki}}\n* {{msg-mw|Importunknownsource}}",
+ "import-token-mismatch": "Used as error message in [[Special:Import]].\n\nSee also:\n* {{msg-mw|import-token-mismatch}}\n* {{msg-mw|import-invalid-interwiki}}\n* {{msg-mw|Importunknownsource}}\n\n{{Identical|Loss of session data}}",
"import-invalid-interwiki": "Used as error message in [[Special:Import]].\n\nSee also:\n* {{msg-mw|import-token-mismatch}}\n* {{msg-mw|import-invalid-interwiki}}\n* {{msg-mw|Importunknownsource}}",
"import-error-edit": "Import error message displayed when importing user has no edit rights for a page.\n\nParameters:\n* $1 - a page name\n{{Related|Import-error}}",
"import-error-create": "Import error message displayed when importing user has no create rights for a page.\n\nParameters:\n* $1 - a page name\n{{Related|Import-error}}",
"expand_templates_generate_xml": "Used as checkbox label.",
"expand_templates_generate_rawhtml": "Used as checkbox label.",
"expand_templates_preview": "{{Identical|Preview}}",
- "expand_templates_preview_fail_html": "Used as error message in Preview section of [[Special:ExpandTemplates]] page.",
+ "expand_templates_preview_fail_html": "Used as error message in Preview section of [[Special:ExpandTemplates]] page.\n\n{{Identical|Loss of session data}}",
"expand_templates_preview_fail_html_anon": "Used as error message in Preview section of [[Special:ExpandTemplates]] page.",
"expand_templates_input_missing": "Used on [[Special:ExpandTemplates]] as an error message, if no input text was provided.",
"pagelanguage": "Title for page Special:PageLanguage",
"thu": "чет-čet",
"fri": "pet-пет",
"sat": "sub-суб",
- "january": "januar-сијечањ",
- "february": "februar-вељача",
- "march": "mart-ожујак",
- "april": "april-травањ",
- "may_long": "maj-свибањ",
- "june": "jun-липањ",
- "july": "jul-српањ",
- "august": "avgust-коловоз",
- "september": "septembar-рујан",
- "october": "oktobar-листопад",
- "november": "студени-novembar",
- "december": "decembar-просинац",
- "january-gen": "januara-сијечња",
- "february-gen": "februara-вељаче",
- "march-gen": "marta-ожујка",
- "april-gen": "aprila-травња",
- "may-gen": "маја-свибња",
- "june-gen": "junа-липња",
- "july-gen": "jula-српња",
- "august-gen": "augusta-коловоза",
- "september-gen": "septembra-рујна",
- "october-gen": "oktobra-листопада",
- "november-gen": "студенога-novembra",
- "december-gen": "decembra-просинца",
- "jan": "jan-сиј",
- "feb": "feb-вељ",
- "mar": "mar-ожу",
- "apr": "apr-тра",
- "may": "maj-сви",
- "jun": "jun-лип",
- "jul": "jul-срп",
- "aug": "aug-кол",
- "sep": "sep-руј",
- "oct": "okt-лис",
- "nov": "сту-nov",
- "dec": "dec-про",
- "january-date": "$1. januar",
- "february-date": "$1. februar",
- "march-date": "$1. mart",
- "april-date": "$1. april",
- "may-date": "$1. maj",
- "june-date": "$1. jun",
- "july-date": "$1. jul",
- "august-date": "$1. august",
- "september-date": "$1. septembar",
- "october-date": "$1. oktobar",
- "november-date": "$1. novembar",
- "december-date": "$1. decembar",
+ "january": "januar",
+ "february": "februar",
+ "march": "mart",
+ "april": "april",
+ "may_long": "maj",
+ "june": "juni",
+ "july": "juli",
+ "august": "august",
+ "september": "septembar",
+ "october": "oktobar",
+ "november": "novembar",
+ "december": "decembar",
+ "january-gen": "januara",
+ "february-gen": "februara",
+ "march-gen": "marta",
+ "april-gen": "aprila",
+ "may-gen": "maja",
+ "june-gen": "juna",
+ "july-gen": "jula",
+ "august-gen": "augusta",
+ "september-gen": "septembra",
+ "october-gen": "oktobra",
+ "november-gen": "novembra",
+ "december-gen": "decembra",
+ "jan": "jan.",
+ "feb": "feb.",
+ "mar": "mar.",
+ "apr": "apr.",
+ "may": "maj",
+ "jun": "jun.",
+ "jul": "jul.",
+ "aug": "aug.",
+ "sep": "sep.",
+ "oct": "okt.",
+ "nov": "nov.",
+ "dec": "dec.",
+ "january-date": "$1. januara",
+ "february-date": "$1. februara",
+ "march-date": "$1. marta",
+ "april-date": "$1. aprila",
+ "may-date": "$1. maja",
+ "june-date": "$1. juna",
+ "july-date": "$1. jula",
+ "august-date": "$1. augusta",
+ "september-date": "$1. septembra",
+ "october-date": "$1. oktobra",
+ "november-date": "$1. novembra",
+ "december-date": "$1. decembra",
"pagecategories": "{{PLURAL:$1|Kategorija|Kategorije}}",
"category_header": "Stranice u kategoriji \"$1\"",
"subcategories": "Potkategorije",
"noindex-category": "Neindeksirane stranice",
"broken-file-category": "Stranice sa neispravnim linkovima do datoteka",
"about": "O...",
- "article": "Stranica sadržaja (članak)",
+ "article": "Stranica sa sadržajem",
"newwindow": "(otvara se u novom prozoru)",
- "cancel": "Odustani - Одустани",
+ "cancel": "Otkaži",
"moredotdotdot": "Još...",
"morenotlisted": "Ovaj spisak nije kompletan.",
"mypage": "Moja stranica",
"errorpagetitle": "Greška - Грешка",
"returnto": "Povratak na $1.",
"tagline": "Izvor: {{SITENAME}}",
- "help": "Pomoć / Помоћ",
+ "help": "Pomoć",
"search": "Traži / Тражи",
"searchbutton": "Traži",
"go": "Idi / Иди",
"history": "Historija stranice",
"history_short": "Historija",
"updatedmarker": "promjene od moje zadnje posjete",
- "printableversion": "Za štampanje / За штампање",
+ "printableversion": "Verzija za ispis",
"permalink": "Trajni link",
"print": "Štampa",
"view": "Vidi",
"talkpagelinktext": "Razgovor",
"specialpage": "Posebna stranica",
"personaltools": "Lični alati",
- "articlepage": "Pogledaj stranicu sa sadržajem (članak)",
+ "articlepage": "Vidi stranicu sa sadržajem",
"talk": "Razgovor",
"views": "Pregledi",
- "toolbox": "Alatke / Алатке",
+ "toolbox": "Alati",
"userpage": "Pogledaj korisničku stranicu - Погледај корисничку страницу",
"projectpage": "Pogledajte stranicu projekta",
"imagepage": "Vidi stranicu datoteke/fajla",
"viewhelppage": "Pogledajte stranicu za pomoć",
"categorypage": "Pogledaj stranicu kategorije",
"viewtalkpage": "Pogledajte raspravu",
- "otherlanguages": "Drugi jezici / Други језици",
+ "otherlanguages": "Na drugim jezicima",
"redirectedfrom": "(Preusmjereno sa $1)",
"redirectpagesub": "Preusmjeri stranicu",
"redirectto": "Preusmjerenje na:",
"copyrightpage": "{{ns:project}}:Autorska_prava",
"currentevents": "Trenutni događaji",
"currentevents-url": "Project:Novosti",
- "disclaimers": "Odricanje odgovornosti",
- "disclaimerpage": "Project:Uslovi korištenja, pravne napomene i odricanje odgovornosti",
+ "disclaimers": "Odricanje od odgovornosti",
+ "disclaimerpage": "Project:Opće odricanje od odgovornosti",
"edithelp": "Pomoć pri uređivanju",
"helppage-top-gethelp": "Pomoć",
"mainpage": "Glavna stranica / Главна страница",
"policy-url": "Project:Pravila",
"portal": "Portal zajednice",
"portal-url": "Project:Portal_zajednice",
- "privacy": "Politika privatnosti - Политика приватности",
+ "privacy": "Politika privatnosti",
"privacypage": "Project:Pravila o anonimnosti",
"badaccess": "Greška pri odobrenju",
"badaccess-group0": "Nije vam dozvoljeno izvršiti akciju koju ste zahtjevali.",
"sort-descending": "Poredaj opadajuće",
"sort-ascending": "Poredaj rastuće",
"nstab-main": "Članak / Чланак",
- "nstab-user": "Korisnik / Корисник",
+ "nstab-user": "Stranica korisnika",
"nstab-media": "Mediji",
"nstab-special": "Posebna stranica",
"nstab-project": "Stranica projekta",
"nstab-image": "Datoteka",
"nstab-mediawiki": "Poruka / Порука",
"nstab-template": "Šablon / Шаблон",
- "nstab-help": "Pomoć / Помоћ",
+ "nstab-help": "Pomoć",
"nstab-category": "Kategorija / Категорија",
"mainpage-nstab": "Glavna stranica / Главна страница",
"nosuchaction": "Nema takve akcije",
"resetpass_submit": "Odredi lozinku i prijavi se",
"changepassword-success": "Vaša šifra je uspiješno promjenjena! Prijava u toku...",
"changepassword-throttled": "Previše puta ste se pokušali prijaviti.\nMolimo Vas da sačekate $1 prije nego što pokušate ponovo.",
+ "botpasswords-label-cancel": "Otkaži",
"resetpass_forbidden": "Šifre ne mogu biti promjenjene",
"resetpass-no-info": "Morate biti prijavljeni da bi ste pristupili ovoj stranici direktno.",
"resetpass-submit-loggedin": "Promijeni lozinku",
- "resetpass-submit-cancel": "Odustani",
+ "resetpass-submit-cancel": "Otkaži",
"resetpass-wrong-oldpass": "Privremena ili trenutna lozinka nije valjana.\nMožda ste već uspješno promijenili Vašu lozinku ili ste tražili novu privremenu lozinku.",
"resetpass-recycled": "Molimo resetirajte vašu lozinku/zaporku u nešto drugo od vaše trenutne lozinke/zaporke.",
"resetpass-temp-emailed": "Prijavili ste se sa privremenim kodom iz e-pošte.\nDa biste završili prijavljivanje morate postaviti novu lozinku ovde:",
"hr_tip": "Horizontalna linija (koristite rijetko)",
"summary": "Sažetak:",
"subject": "Tema:",
- "minoredit": "Mala izmjena - Мала измена",
- "watchthis": "Prati / Прати",
- "savearticle": "Sačuvaj - Сачувај",
- "preview": "Pretpregled / Претпреглед",
- "showpreview": "Pretpregled - Претпреглед",
- "showdiff": "Prikaži izmjene - Прикажи измене",
+ "minoredit": "Ovo je manje uređenje",
+ "watchthis": "Motri ovu stranicu",
+ "savearticle": "Spremi stranicu",
+ "preview": "Pregled",
+ "showpreview": "Prikaži pregled",
+ "showdiff": "Prikaži izmjene",
"blankarticle": "<strong>Upozorenje:</strong> Napravili ste praznu stranicu.\nAko ponovno kliknete \"{{int:savearticle}}\", napravit ćete praznu stranicu bez sadržaja.",
"anoneditwarning": "<strong>Upozorenje:</strong> Niste prijavljeni. \nVaša IP adresa će biti javno vidljiva ako napravite neku izmjenu. Ako se <strong>[$1 prijavite]</strong> ili <strong>[$2 napravite račun]</strong>, vaše izmjene će biti pripisane vašem korisničkom imenu, zajedno sa drugim pogodnostima.",
"anonpreviewwarning": "''Niste prijavljeni. Vaša IP adresa će biti zabilježena u historiji ove stranice.''",
"viewprevnext": "Pogledaj ($1 {{int:pipe-separator}} $2) ($3)",
"searchmenu-exists": "'''Postoji stranica pod nazivom \"[[:$1]]\" na ovoj wiki'''",
"searchmenu-new": "<strong>Napravi stranicu \"[[:$1]]\" na ovoj wiki!</strong> {{PLURAL:$2|0=|Pogledajte također straniu pronađenu vašom pretragom.|Pogledajte također i vaše rezultate pretrage.}}",
- "searchprofile-articles": "Stranice sadržaja",
+ "searchprofile-articles": "Stranice sa sadržajem",
"searchprofile-images": "Multimedija",
"searchprofile-everything": "Sve",
"searchprofile-advanced": "Napredno",
"prefs-edits": "Broj izmjena:",
"prefsnologintext2": "Molimo Vas prijavite se da biste promijenili postavke.",
"prefs-skin": "Izgled (skin)",
- "skin-preview": "Pretpregled",
+ "skin-preview": "Pregled",
"datedefault": "Bez preferenci",
"prefs-labs": "Eksperimentalne mogućnosti",
"prefs-user-pages": "Korisničke stranice",
"prefs-personal": "Korisnički profil",
"prefs-rc": "Podešavanje nedavnih izmjena",
- "prefs-watchlist": "Praćene stranice / Списак надгледања",
+ "prefs-watchlist": "Lista motrenja",
"prefs-editwatchlist": "Uredi popis praćenja",
"prefs-editwatchlist-label": "Uredi unose na popisu praćenja:",
"prefs-editwatchlist-edit": "Vidite i uklonite naslove na vašem popisu praćenja",
"prefs-timeoffset": "Vremenska razlika",
"prefs-advancedediting": "Opće opcije",
"prefs-editor": "Uređivač",
- "prefs-preview": "Pretpregled",
+ "prefs-preview": "Pregled",
"prefs-advancedrc": "Napredne opcije",
"prefs-advancedrendering": "Napredne opcije",
"prefs-advancedsearchoptions": "Napredne opcije",
"right-createtalk": "Pravljenje stranica za razgovor",
"right-createaccount": "Pravljenje korisničkog računa",
"right-minoredit": "Označavanje izmjena kao malih",
- "right-move": "Preusmjeravanje stranica",
+ "right-move": "Premještanje stranica",
"right-move-subpages": "Preusmjeravanje stranica sa svim podstranicama",
"right-move-rootuserpages": "Premještanje stranica osnovnih korisnika",
"right-move-categorypages": "Pomakni stranice kategorije",
"rc-enhanced-hide": "Sakrij detalje",
"rc-old-title": "prvobitno kreirano kao \"$1\"",
"recentchangeslinked": "Srodne izmjene / Сродне измене",
- "recentchangeslinked-feed": "Srodne izmjene",
- "recentchangeslinked-toolbox": "Srodne izmjene",
- "recentchangeslinked-title": "Srodne promjene sa \"$1\"",
+ "recentchangeslinked-feed": "Vezane izmjene",
+ "recentchangeslinked-toolbox": "Vezane izmjene",
+ "recentchangeslinked-title": "Izmjene vezane s \"$1\"",
"recentchangeslinked-summary": "Ova posebna stranica prikazuje promjene na povezanim stranicama.\nStranice koje su na vašem [[Special:Watchlist|spisku praćenja]] su '''podebljane'''.",
"recentchangeslinked-page": "Naslov stranice:",
"recentchangeslinked-to": "Pokaži promjene stranica koji su povezane sa datom stranicom",
"upload-http-error": "Desila se HTTP greška: $1",
"upload-copy-upload-invalid-domain": "Kopije postavljanja nisu dostupni na ovom domenu.",
"upload-dialog-title": "Postavi datoteku",
- "upload-dialog-button-cancel": "Odustani",
+ "upload-dialog-button-cancel": "Otkaži",
"upload-dialog-button-done": "Urađeno",
"upload-dialog-button-save": "Snimi",
"upload-dialog-button-upload": "Postavi",
"statistics-header-edits": "Statistike izmjena",
"statistics-header-users": "Statistike korisnika",
"statistics-header-hooks": "Ostale statistike",
- "statistics-articles": "Stranice sadržaja",
+ "statistics-articles": "Stranice sa sadržajem",
"statistics-pages": "Stranice",
"statistics-pages-desc": "Sve stranice na wikiju, uključujući stranice za razgovor, preusmjerenja itd.",
"statistics-files": "Broj postavljenih datoteka",
"trackingcategories-disabled": "Kategorija je onemogućena",
"mailnologin": "Nema adrese za slanje",
"mailnologintext": "Morate biti [[Special:UserLogin|prijavljeni]] i imati ispravnu adresu e-pošte u vašim [[Special:Preferences|podešavanjima]] da biste slali e-poštu drugim korisnicima.",
- "emailuser": "Pošalji E-mail ovom korisniku",
+ "emailuser": "Pošalji e-mail ovom korisniku",
"emailuser-title-target": "Slanje e-maila {{GENDER:$1|korisniku|korisnici|korisniku}}",
"emailuser-title-notarget": "Slanje e-maila korisniku",
"emailpagetext": "Možete da koristite donji obrazac da pošaljete e-mail {{GENDER:$1|ovom korisniku|ovoj korisnici|ovom korisniku|}}.\nE-mail koju ste uneli u vašim [[Special:Preferences|postavkama]] će se prikazati u polju \"Od:\", tako da će primalac moći da vam odgovori direktno.",
"emailuserfooter": "Ovu e-poruku {{GENDER:$1|poslao|poslala}} je $1 {{GENDER:$2|korisniku|korisnici}} $2 pomoću funkcije \"{{int:emailuser}}\" s projekta {{SITENAME}}.",
"usermessage-summary": "Ostavljanje sistemske poruke.",
"usermessage-editor": "Sistem za poruke",
- "watchlist": "Spisak praćenja / Списак праћења",
+ "watchlist": "Lista motrenja",
"mywatchlist": "Lista motrenja",
"watchlistfor2": "Za $1 $2",
"nowatchlist": "Nemate ništa na svom spisku praćenih članaka.",
"removewatch": "Ukloni sa spiska praćenja",
"removedwatchtext": "Stranica „[[:$1]]“ i njena stranica za razgovor je uklonjena s vašeg [[Special:Watchlist|spiska nadgledanja]].",
"removedwatchtext-short": "Stranica \"$1\" je uklonjena sa vašeg spiska praćenja.",
- "watch": "Prati / Прати",
- "watchthispage": "Prati / Прати",
+ "watch": "Motri",
+ "watchthispage": "Motri ovu stranicu",
"unwatch": "Prekini praćenje",
- "unwatchthispage": "Ukinite praćenje",
- "notanarticle": "Nije članak",
+ "unwatchthispage": "Prestani motriti",
+ "notanarticle": "Nije stranica sa sadržajem",
"notvisiblerev": "Posljednja izmjena drugog korisnika je bila izbrisana",
"watchlist-details": "{{PLURAL:$1|$1 stranica|$1 stranice|$1 stranica }} na vašem spisku praćenja, ne računajući posebno stranice za razgovor.",
"wlheader-enotif": "* Obavještavanje e-poštom je omogućeno.",
"sp-contributions-toponly": "Prikaži samo izmjene koje su posljednje revizije",
"sp-contributions-newonly": "Prikaži samo izmjene kojima su napravljene nove stranice",
"sp-contributions-submit": "Traži",
- "whatlinkshere": "Što vodi ovdje / Шта води овде",
+ "whatlinkshere": "Što upućuje ovamo",
"whatlinkshere-title": "Stranice koje vode / Странице које воде до $1",
"whatlinkshere-page": "Stranica:",
"linkshere": "Sljedeće stranice vode na '''[[:$1]]''':",
"databasenotlocked": "Baza podataka nije zaključana.",
"lockedbyandtime": "(od $1 dana $2 u $3)",
"move-page": "Preusmjeravanje $1",
- "move-page-legend": "Premjestite stranicu",
+ "move-page-legend": "Premjesti stranicu",
"movepagetext": "Korištenjem ovog formulara možete preimenovati stranicu, premještajući cijelu historiju na novo ime.\nČlanak pod starim imenom će postati stranica koja preusmjerava na članak pod novim imenom. \nMožete automatski izmjeniti preusmjerenje do izvornog naslova.\nAko se ne odlučite na to, provjerite [[Special:DoubleRedirects|dvostruka]] ili [[Special:BrokenRedirects|neispravna preusmjeravanja]].\nDužni ste provjeriti da svi linkovi i dalje nastave voditi na prave stranice.\n\nImajte na umu da članak '''neće''' biti preusmjeren ukoliko već postoji članak pod imenom na koje namjeravate da preusmjerite osim u slučaju stranice za preusmjeravanje koja nema nikakvih starih izmjena.\nTo znači da možete vratiti stranicu na prethodno mjesto ako pogriješite, ali ne možete zamijeniti postojeću stranicu.\n\n'''Pažnja!'''\nOvo može biti drastična i neočekivana promjena kad su u pitanju popularne stranice;\nMolimo dobro razmislite prije nego što preimenujete stranicu.",
"movepagetext-noredirectfixer": "Koristeći obrazac ispod ćete preimenovati stranicu i premjestiti cijelu njenu historiju na novi naziv.\nStari naziv će postati preusmjerenje na novi naziv.\nMolimo provjerite da li postoje [[Special:DoubleRedirects|dvostruka]] ili [[Special:BrokenRedirects|nedovršena preusmjerenja]].\nVi ste za to odgovorni te morate provjeriti da li su linkovi ispravni i da li vode tamo gdje bi trebali.\n\nImajte na umu da stranica '''neće''' biti premještena ako već postoji stranica s tim imenom, osim ako je prazna ili je preusmjerenje ili nema ranije historije.\nOvo znali da možete preimenovati stranicu nazad gdje je ranije bila preimenovana ako ste pogriješili a ne možete ponovo preimenovati postojeću stranicu.\n\n'''Pažnja!'''\nImajte na umu da preusmjeravanje popularnog članka može biti\ndrastična i neočekivana promjena za korisnike; molimo budite sigurni da ste shvatili posljedice prije nego što nastavite.",
"movepagetalktext": "Ako označite ovu kutijucu, pridružena stranica za razgovor će se automatski premjestiti na novi naslov, ukoliko ne-prazna stranica razgovor sa istim imenom već postoji. U tom slučaju ćete morati, ako želite, ručno premjestiti ili spojiti stranicu.",
"cant-move-category-page": "Nemate dopuštene da premještate stranice kategorija.",
"cant-move-to-category-page": "Nemate dopuštenje da premjestite stranicu na stranicu kategorije.",
"newtitle": "Novi naslov:",
- "move-watch": "Prati ovu stranicu - Прати ову страницу",
- "movepagebtn": "Premjesti stranicu – Премјести страницу",
+ "move-watch": "Motri izvornu i ciljnu stranicu",
+ "movepagebtn": "Premjesti stranicu",
"pagemovedsub": "Premještanje uspjelo",
"movepage-moved": "'''\"$1\" je premještena na \"$2\"'''",
"movepage-moved-redirect": "Preusmjerenje je napravljeno.",
"movepage-moved-noredirect": "Pravljenje preusmjerenja je onemogućeno.",
"articleexists": "Stranica pod tim imenom već postoji, ili je ime koje ste izabrali neispravno.\nMolimo Vas da izaberete drugo ime.",
"cantmove-titleprotected": "Ne možete premjestiti stranicu na ovu lokaciju, jer je novi naslov zaštićen od pravljenja",
- "movetalk": "Premjesti i stranicu za diskusiju zajedno sa člankom (ako nije prazna).",
- "move-subpages": "Premjesti sve podstranice (do $1)",
- "move-talk-subpages": "Premjesti podstranice stranica za razgovor (do $1)",
+ "movetalk": "Premjesti pridruženu stranicu za razgovor",
+ "move-subpages": "Premjesti podstranice (sve do $1)",
+ "move-talk-subpages": "Premjesti podstranice stranice za razgovor (sve do $1)",
"movepage-page-exists": "Stranica $1 već postoji i ne može biti automatski zamijenjena.",
"movepage-page-moved": "Stranica $1 je premještena na $2.",
"movepage-page-unmoved": "Stranica $1 ne može biti premještena na $2.",
"nonfile-cannot-move-to-file": "Ne mogu se premjestiti podaci u datotečni imenski prostor",
"imagetypemismatch": "Ekstenzija nove datoteke ne odgovara njenom tipu",
"imageinvalidfilename": "Ciljno ime datoteke nije valjano",
- "fix-double-redirects": "Ažuriraj sva preusmjerenja koja vode ka originalnom naslovu",
+ "fix-double-redirects": "Ažuriraj sva preusmjerenja koja vode na originalni naslov",
"move-leave-redirect": "Ostavi preusmjerenje",
"protectedpagemovewarning": "'''Upozorenje:''' Ova stranica je zaključana tako da je mogu premještati samo korisnici sa ovlastima administratora.\nPosljednja stavka evidencije je prikazana ispod kao referenca:",
"semiprotectedpagemovewarning": "'''Napomena:''' Ova stranica je zaključana tako da je mogu uređivati samo registrovani korisnici.\nPosljednja stavka evidencije je prikazana ispod kao referenca:",
"tooltip-pt-login": "Predlažem da se prijavite; međutim, to nije obavezno",
"tooltip-pt-logout": "Odjava sa projekta {{SITENAME}}",
"tooltip-pt-createaccount": "Ohrabrujemo vas da otvorite račun i prijavite se; to, međutim, nije obavezno",
- "tooltip-ca-talk": "Razgovor o sadržaju stranice",
+ "tooltip-ca-talk": "Rasprava o stranici sa sadržajem",
"tooltip-ca-edit": "Uredi ovu stranicu",
"tooltip-ca-addsection": "Započnite novu sekciju.",
"tooltip-ca-viewsource": "Ova stranica je zaštićena.\nMožete vidjeti njen izvor",
"tooltip-ca-delete": "Izbriši ovu stranicu",
"tooltip-ca-undelete": "Vratite izmjene koje su načinjene prije brisanja stranice",
"tooltip-ca-move": "Premjesti ovu stranicu",
- "tooltip-ca-watch": "Dodajte ovu stranicu na Vaš spisak praćenja",
+ "tooltip-ca-watch": "Dodaj ovu stranicu na svoju listu motrenja",
"tooltip-ca-unwatch": "Izbrišite ovu stranicu sa spiska praćenja",
"tooltip-search": "Traži ovaj Wiki / Тражи овај Вики [alt-f]",
"tooltip-search-go": "Idi na stranicu s upravo ovakvim imenom ako postoji",
"tooltip-n-currentevents": "Pronađi dodatne informacije o trenutnim događajima",
"tooltip-n-recentchanges": "Spisak nedavnih izmjena na wikiju.",
"tooltip-n-randompage": "Otvorite slučajnu stranicu",
- "tooltip-n-help": "Mjesto gdje možete nešto da naučite",
- "tooltip-t-whatlinkshere": "Spisak svih stranica povezanih sa ovim",
+ "tooltip-n-help": "Mjesto za saznavanje",
+ "tooltip-t-whatlinkshere": "Lista svih wiki stranica koje upućuju ovamo",
"tooltip-t-recentchangeslinked": "Nedavne izmjene ovdje povezanih stranica",
"tooltip-feed-rss": "RSS feed za ovu stranicu",
"tooltip-feed-atom": "Atom feed za ovu stranicu",
"tooltip-t-contributions": "Pogledajte listu doprinosa ovog korisnika",
"tooltip-t-emailuser": "Pošaljite e-mail ovom korisniku",
"tooltip-t-upload": "Postavi datoteke",
- "tooltip-t-specialpages": "Popis svih posebnih stranica",
- "tooltip-t-print": "Verzija ove stranice za štampanje",
+ "tooltip-t-specialpages": "Lista svih posebnih stranica",
+ "tooltip-t-print": "Verzija ove stranice za ispis",
"tooltip-t-permalink": "Stalni link ove verzije stranice",
- "tooltip-ca-nstab-main": "Pogledajte sadržaj stranice",
+ "tooltip-ca-nstab-main": "Vidi stranicu sa sadržajem",
"tooltip-ca-nstab-user": "Pogledajte korisničku stranicu",
"tooltip-ca-nstab-media": "Pogledajte medijski fajl",
"tooltip-ca-nstab-special": "Ovo je posebna stranica, te se ne može zasebno uređivati",
"tooltip-ca-nstab-image": "Vidi stranicu datoteke/fajla",
"tooltip-ca-nstab-mediawiki": "Pogledajte sistemsku poruku",
"tooltip-ca-nstab-template": "Pogledajte šablon",
- "tooltip-ca-nstab-help": "Pogledajte stranicu za pomoć",
+ "tooltip-ca-nstab-help": "Vidi stranicu pomoći",
"tooltip-ca-nstab-category": "Pogledajte stranicu kategorije",
- "tooltip-minoredit": "Označite ovo kao manju izmjenu",
- "tooltip-save": "Snimi izmjene - Сними измјене [alt-s]",
- "tooltip-preview": "Prethodni pregled stranice, molimo koristiti prije snimanja!",
+ "tooltip-minoredit": "Označi ovo kao manje uređenje",
+ "tooltip-save": "Spremite svoje izmjene",
+ "tooltip-preview": "Pregledajte svoje izmjene. Molimo vas da ovo koristite prije spremanja.",
"tooltip-diff": "Prikaz izmjena koje ste napravili u tekstu",
"tooltip-compareselectedversions": "Pogledajte pazlike između dvije selektovane verzije ove stranice.",
- "tooltip-watch": "Postavite ovu stranicu na Vaš spisak praćenja / Поставите ову страницу на Ваш списак праћења [alt-w]",
+ "tooltip-watch": "Dodaj ovu stranicu na svoju listu motrenja",
"tooltip-watchlistedit-normal-submit": "Ukloni naslove",
"tooltip-watchlistedit-raw-submit": "Ažuriraj spisak praćenja",
"tooltip-recreate": "Ponovno pravljenje stranice iako je već brisana",
"exif-attributionurl": "Kada ponovno koristite ovaj rad, molimo povežite ga na",
"exif-preferredattributionname": "Kada ponovno koristite ovaj rad, molimo pripišite ga na",
"exif-pngfilecomment": "PNG komentar datoteke",
- "exif-disclaimer": "Odricanje odgovornosti",
+ "exif-disclaimer": "Odricanje od odgovornosti",
"exif-contentwarning": "Upozorenje o sadržaju",
"exif-giffilecomment": "GIF komentar datoteke",
"exif-intellectualgenre": "Tip predmeta",
"fileduplicatesearch-result-1": "Datoteka \"$1\" nema identičnih dvojnika.",
"fileduplicatesearch-result-n": "Datoteka \"$1\" ima {{PLURAL:$2|1 identičnog|$2 identična|$2 identičnih}} dvojnika.",
"fileduplicatesearch-noresults": "Nije pronađena datoteka sa imenom \"$1\".",
- "specialpages": "Posebno / Посебно",
+ "specialpages": "Posebne stranice",
"specialpages-note": "* Normalne posebne stranice.\n* <span class=\"mw-specialpagerestricted\">Ograničene posebne stranice.</span>\n* <span class=\"mw-specialpagecached\">Keširane posebne stranice (mogu biti zastarjele).</span>",
"specialpages-group-maintenance": "Izvještaji o održavanju / Извјештаји о одржавању",
"specialpages-group-other": "Ostale posebne stranice - Остале посебне странице",
"feedback-bugcheck": "Izvrsno! Molimo provjerite da se ne radi o nekom [$1 poznatom \"bugu\"].",
"feedback-bugnew": "Provereno. Prijavi novu grešku",
"feedback-bugornote": "Ako ste spremni da detaljno opišete tehnički problem, onda [$1 prijavite grešku].\nU suprotnom, poslužite se jednostavnim obrascem ispod. Vaš komentar će stajati na stranici „[$3 $2]“, zajedno s korisničkim imenom i pregledačem koji koristite.",
- "feedback-cancel": "Odustani",
+ "feedback-cancel": "Otkaži",
"feedback-close": "Gotovo",
"feedback-error1": "Greška: neprepoznat rezultat od API-ja",
"feedback-error2": "Greška: Uređivanje nije uspjelo",
"duration-centuries": "$1 {{PLURAL:$1|vijek|vijekova}}",
"duration-millennia": "$1 {{PLURAL:$1|milenijum|milenijuma}}",
"rotate-comment": "Slika rotirana za $1 {{PLURAL:$1|stepeni}} u smjeru kazaljke na satu",
- "expand_templates_input": "Unos - Унос"
+ "expand_templates_input": "Unos - Унос",
+ "expand_templates_preview": "Pregled"
}
"createacct-reason-ph": "Чому ви створюєте інший обліковий запис",
"createacct-submit": "Створіть ваш обліковий запис",
"createacct-another-submit": "Створити обліковий запис",
- "createacct-benefit-heading": "{{SITENAME}} Ñ\81Ñ\82воÑ\80Ñ\8eÑ\94Ñ\82Ñ\8cÑ\81Ñ\8f Ñ\82акими Ñ\81амими лÑ\8eдÑ\8cми, Ñ\8fк Ñ\96 ви.",
+ "createacct-benefit-heading": "{{SITENAME}} Ñ\81Ñ\82воÑ\80Ñ\8eÑ\94Ñ\82Ñ\8cÑ\81Ñ\8f Ñ\82акими Ñ\81амими лÑ\8eдÑ\8cми, Ñ\8fк Ñ\96 Ð\92и.",
"createacct-benefit-body1": "{{PLURAL:$1|редагування|редагування|редагувань}}",
"createacct-benefit-body2": "{{PLURAL:$1|сторінка|сторінки|сторінок}}",
"createacct-benefit-body3": "{{PLURAL:$1|дописувач|дописувачі|дописувачів}} цього місяця",
<?php
-/** Goan Konkani (à¤\97à¥\8bवा à¤\95à¥\8bà¤\82à¤\95णà¥\80 / Gova Konknni)
+/** Goan Konkani (à¤\97à¥\8bà¤\82यà¤\9aà¥\80 à¤\95à¥\8bà¤\82à¤\95णà¥\80 / Gõychi Konknni)
*
* To improve a translation please visit https://translatewiki.net
*
<?php
-/** Goan Konkani - Devanagari script (à¤\97à¥\8bवा कोंकणी)
+/** Goan Konkani - Devanagari script (à¤\97à¥\8bà¤\82यà¤\9aà¥\80 कोंकणी)
*
* To improve a translation please visit https://translatewiki.net
*
public function __construct() {
parent::__construct();
- $this->setDescription( 'Script for periodic off-peak updating of the search index' );
+ $this->addDescription( 'Script for periodic off-peak updating of the search index' );
$this->addOption( 's', 'starting timestamp', false, true );
$this->addOption( 'e', 'Ending timestamp', false, true );
$this->addOption(
@import "mediawiki.mixins";
-// Table Sorting
+/* Table Sorting */
-.client-js table.jquery-tablesorter th.headerSort {
+table.jquery-tablesorter th.headerSort {
.background-image-svg( 'images/sort_both.svg', 'images/sort_both.png' );
cursor: pointer;
- // Keep synchronized with mediawiki.skinning.content styles
background-repeat: no-repeat;
background-position: center right;
padding-right: 21px;
}
-.client-js table.jquery-tablesorter th.headerSortUp {
+table.jquery-tablesorter th.headerSortUp {
.background-image-svg( 'images/sort_up.svg', 'images/sort_up.png' );
}
-.client-js table.jquery-tablesorter th.headerSortDown {
+table.jquery-tablesorter th.headerSortDown {
.background-image-svg( 'images/sort_down.svg', 'images/sort_down.png' );
}
div.tleft {
margin: .5em 1.4em 1.3em 0;
}
-
-/* Make space for the jquery.tablesorter icon and display a placeholder if JavaScript is loaded, while
- tablesorter is still loading and setting up the tables for sorting. This avoids a flash of
- unstyled content during page load (FOUC). The styles can also be used by WYSIWYG editors. */
-.client-js table.sortable th:not(.unsortable) {
- background-image: url(images/sort_both_readonly.png);
- /* @embed */
- background-image: linear-gradient(transparent, transparent), url(images/sort_both_readonly.svg);
- /* Keep synchronised with jquery.tablesorter styles */
- background-repeat: no-repeat;
- background-position: center right;
- padding-right: 21px;
-}
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 9" height="9" width="21">
- <path d="M14.5 5l-4 4-4-4zM14.5 4l-4-4-4 4z" style="fill-opacity: 0.5;"/>
-</svg>
$checkboxes.prop( 'checked', check );
}
- $( '#checkbox-all' ).click( function ( e ) {
+ $( '.mw-checkbox-all' ).click( function ( e ) {
selectAll( true );
e.preventDefault();
} );
- $( '#checkbox-none' ).click( function ( e ) {
+ $( '.mw-checkbox-none' ).click( function ( e ) {
selectAll( false );
e.preventDefault();
} );
- $( '#checkbox-invert' ).click( function ( e ) {
+ $( '.mw-checkbox-invert' ).click( function ( e ) {
$checkboxes.each( function () {
$( this ).prop( 'checked', !$( this ).is( ':checked' ) );
} );
}
public function testUpdatePage() {
+ $checkIds = array();
+
+ $this->setMwGlobals( 'wgHooks', array(
+ 'PageContentInsertComplete' => array( function (
+ WikiPage &$page, User &$user, Content $content,
+ $summary, $minor, $u1, $u2, &$flags, Revision $revision
+ ) {
+ // types/refs checked
+ } ),
+ 'PageContentSaveComplete' => array( function (
+ WikiPage &$page, User &$user, Content $content,
+ $summary, $minor, $u1, $u2, &$flags, Revision $revision,
+ Status &$status, $baseRevId
+ ) use ( &$checkIds ) {
+ $checkIds[] = $status->value['revision']->getId();
+ // types/refs checked
+ } ),
+ ) );
+
$text = "one";
$edit = array(
'wpTextbox1' => $text,
$page = $this->assertEdit( 'EditPageTest_testUpdatePage', "zero", null, $edit,
EditPage::AS_SUCCESS_UPDATE, $text,
"expected successfull update with given text" );
+ $this->assertGreaterThan( 0, $checkIds[0], "First event rev ID set" );
$this->forceRevisionDate( $page, '20120101000000' );
$this->assertEdit( 'EditPageTest_testUpdatePage', null, null, $edit,
EditPage::AS_SUCCESS_UPDATE, $text,
"expected successfull update with given text" );
+ $this->assertGreaterThan( 0, $checkIds[1], "Second edit hook rev ID set" );
+ $this->assertGreaterThan( $checkIds[0], $checkIds[1], "Second event rev ID is higher" );
+ }
+
+ public function testUpdatePageTrx() {
+ $text = "one";
+ $edit = array(
+ 'wpTextbox1' => $text,
+ 'wpSummary' => 'first update',
+ );
+
+ $page = $this->assertEdit( 'EditPageTest_testTrxUpdatePage', "zero", null, $edit,
+ EditPage::AS_SUCCESS_UPDATE, $text,
+ "expected successfull update with given text" );
+
+ $this->forceRevisionDate( $page, '20120101000000' );
+
+ $checkIds = array();
+ $this->setMwGlobals( 'wgHooks', array(
+ 'PageContentSaveComplete' => array( function (
+ WikiPage &$page, User &$user, Content $content,
+ $summary, $minor, $u1, $u2, &$flags, Revision $revision,
+ Status &$status, $baseRevId
+ ) use ( &$checkIds ) {
+ $checkIds[] = $status->value['revision']->getId();
+ // types/refs checked
+ } ),
+ ) );
+
+ wfGetDB( DB_MASTER )->begin( __METHOD__ );
+
+ $text = "two";
+ $edit = array(
+ 'wpTextbox1' => $text,
+ 'wpSummary' => 'second update',
+ );
+
+ $this->assertEdit( 'EditPageTest_testTrxUpdatePage', null, null, $edit,
+ EditPage::AS_SUCCESS_UPDATE, $text,
+ "expected successfull update with given text" );
+
+ $text = "three";
+ $edit = array(
+ 'wpTextbox1' => $text,
+ 'wpSummary' => 'third update',
+ );
+
+ $this->assertEdit( 'EditPageTest_testTrxUpdatePage', null, null, $edit,
+ EditPage::AS_SUCCESS_UPDATE, $text,
+ "expected successfull update with given text" );
+
+ wfGetDB( DB_MASTER )->commit( __METHOD__ );
+
+ $this->assertGreaterThan( 0, $checkIds[0], "First event rev ID set" );
+ $this->assertGreaterThan( 0, $checkIds[1], "Second edit hook rev ID set" );
+ $this->assertGreaterThan( $checkIds[0], $checkIds[1], "Second event rev ID is higher" );
}
public static function provideSectionEdit() {
--- /dev/null
+<?php
+
+/**
+ * @group Database
+ */
+class MergeHistoryTest extends MediaWikiTestCase {
+
+ /**
+ * Make some pages to work with
+ */
+ public function addDBData() {
+ // Pages that won't actually be merged
+ $this->insertPage( 'Test' );
+ $this->insertPage( 'Test2' );
+
+ // Pages that will be merged
+ $this->insertPage( 'Merge1' );
+ $this->insertPage( 'Merge2' );
+ }
+
+ /**
+ * @dataProvider provideIsValidMerge
+ * @covers MergeHistory::isValidMerge
+ * @param $source string Source page
+ * @param $dest string Destination page
+ * @param $timestamp string|bool Timestamp up to which revisions are merged (or false for all)
+ * @param $error string|bool Expected error for test (or true for no error)
+ */
+ public function testIsValidMerge( $source, $dest, $timestamp, $error ) {
+ $this->setMwGlobals( 'wgContentHandlerUseDB', false );
+ $mh = new MergeHistory(
+ Title::newFromText( $source ),
+ Title::newFromText( $dest ),
+ $timestamp
+ );
+ $status = $mh->isValidMerge();
+ if ( $error === true ) {
+ $this->assertTrue( $status->isGood() );
+ } else {
+ $this->assertTrue( $status->hasMessage( $error ) );
+ }
+ }
+
+ public static function provideIsValidMerge() {
+ return array(
+ // for MergeHistory::isValidMerge
+ array( 'Test', 'Test2', false, true ),
+ // Although this timestamp is after the latest timestamp of both pages,
+ // MergeHistory should select the latest source timestamp up to this which should
+ // still work for the merge.
+ array( 'Test', 'Test2', strtotime( 'tomorrow' ), true ),
+ array( 'Test', 'Test', false, 'mergehistory-fail-self-merge' ),
+ array( 'Nonexistant', 'Test2', false, 'mergehistory-fail-invalid-source' ),
+ array( 'Test', 'Nonexistant', false, 'mergehistory-fail-invalid-dest' ),
+ array(
+ 'Test',
+ 'Test2',
+ 'This is obviously an invalid timestamp',
+ 'mergehistory-fail-bad-timestamp'
+ ),
+ );
+ }
+
+ /**
+ * Test merge revision limit checking
+ * @covers MergeHistory::isValidMerge
+ */
+ public function testIsValidMergeRevisionLimit() {
+ $limit = MergeHistory::REVISION_LIMIT;
+
+ $mh = $this->getMockBuilder( 'MergeHistory' )
+ ->setMethods( array( 'getRevisionCount' ) )
+ ->setConstructorArgs( array(
+ Title::newFromText( 'Test' ),
+ Title::newFromText( 'Test2' ),
+ ) )
+ ->getMock();
+ $mh->expects( $this->once() )
+ ->method( 'getRevisionCount' )
+ ->will( $this->returnValue( $limit + 1 ) );
+
+ $status = $mh->isValidMerge();
+ $this->assertTrue( $status->hasMessage( 'mergehistory-fail-toobig' ) );
+ $errors = $status->getErrorsByType( 'error' );
+ $params = $errors[0]['params'];
+ $this->assertEquals( $params[0], Message::numParam( $limit ) );
+ }
+
+ /**
+ * Test user permission checking
+ * @covers MergeHistory::checkPermissions
+ */
+ public function testCheckPermissions() {
+ $mh = new MergeHistory(
+ Title::newFromText( 'Test' ),
+ Title::newFromText( 'Test2' )
+ );
+
+ // Sysop with mergehistory permission
+ $sysop = User::newFromName( 'UTSysop' );
+ $status = $mh->checkPermissions( $sysop, '' );
+ $this->assertTrue( $status->isOK() );
+
+ // Normal user
+ $notSysop = User::newFromName( 'UTNotSysop' );
+ $notSysop->addToDatabase();
+ $status = $mh->checkPermissions( $notSysop, '' );
+ $this->assertTrue( $status->hasMessage( 'mergehistory-fail-permission' ) );
+ }
+
+ /**
+ * Test merged revision count
+ * @covers MergeHistory::getMergedRevisionCount
+ */
+ public function testGetMergedRevisionCount() {
+ $mh = new MergeHistory(
+ Title::newFromText( 'Merge1' ),
+ Title::newFromText( 'Merge2' )
+ );
+
+ $mh->merge( User::newFromName( 'UTSysop' ) );
+ $this->assertEquals( $mh->getMergedRevisionCount(), 1 );
+ }
+}
CONTENT_MODEL_CSS => 'CssContentHandler',
CONTENT_MODEL_TEXT => 'TextContentHandler',
'testing' => 'DummyContentHandlerForTesting',
+ 'testing-callbacks' => function( $modelId ) {
+ return new DummyContentHandlerForTesting( $modelId );
+ }
),
) );
return true;
}
+
+ public function provideGetModelForID() {
+ return array(
+ array( CONTENT_MODEL_WIKITEXT, 'WikitextContentHandler' ),
+ array( CONTENT_MODEL_JAVASCRIPT, 'JavaScriptContentHandler' ),
+ array( CONTENT_MODEL_JSON, 'JsonContentHandler' ),
+ array( CONTENT_MODEL_CSS, 'CssContentHandler' ),
+ array( CONTENT_MODEL_TEXT, 'TextContentHandler' ),
+ array( 'testing', 'DummyContentHandlerForTesting' ),
+ array( 'testing-callbacks', 'DummyContentHandlerForTesting' ),
+ );
+ }
+
+ /**
+ * @dataProvider provideGetModelForID
+ */
+ public function testGetModelForID( $modelId, $handlerClass ) {
+ $handler = ContentHandler::getForModelID( $modelId );
+
+ $this->assertInstanceOf( $handlerClass, $handler );
+ }
+
}
'styles' => array(),
'messages' => new XmlJsCode( '{}' ),
'templates' => array(),
- 'title' => 'scripts, styles and messags',
'expected' => 'mw.loader.implement( "test.example", function ( $, jQuery ) {
mw.example();
}
public function testCheckSessionInfo() {
- $logger = new \TestLogger( true, function ( $m ) {
- return preg_replace(
- '/^Session \[\d+\][a-zA-Z0-9_\\\\]+<(?:null|anon|[+-]:\d+:\w+)>\w+: /', 'Session X: ', $m
- );
- } );
+ $logger = new \TestLogger( true );
$provider = $this->getProvider();
$provider->setLogger( $logger );
$this->assertFalse( $provider->refreshSessionInfo( $info, $request, $metadata ) );
$this->assertSame( array(
- array( LogLevel::INFO, "Session X: Missing metadata: $key" )
+ array( LogLevel::INFO, 'Session "{session}": Missing metadata: {missing}' )
), $logger->getBuffer() );
$logger->clearBuffer();
}
$metadata = $info->getProviderMetadata();
$this->assertFalse( $provider->refreshSessionInfo( $info, $request, $metadata ) );
$this->assertSame( array(
- array( LogLevel::INFO, "Session X: No BotPassword for {$bp->getUserCentralId()} Foobar" ),
+ array( LogLevel::INFO, 'Session "{session}": No BotPassword for {centralId} {appId}' ),
), $logger->getBuffer() );
$logger->clearBuffer();
$metadata = $info->getProviderMetadata();
$this->assertFalse( $provider->refreshSessionInfo( $info, $request, $metadata ) );
$this->assertSame( array(
- array( LogLevel::INFO, 'Session X: BotPassword token check failed' ),
+ array( LogLevel::INFO, 'Session "{session}": BotPassword token check failed' ),
), $logger->getBuffer() );
$logger->clearBuffer();
$metadata = $info->getProviderMetadata();
$this->assertFalse( $provider->refreshSessionInfo( $info, $request2, $metadata ) );
$this->assertSame( array(
- array( LogLevel::INFO, 'Session X: Restrictions check failed' ),
+ array( LogLevel::INFO, 'Session "{session}": Restrictions check failed' ),
), $logger->getBuffer() );
$logger->clearBuffer();
use MediaWikiTestCase;
use User;
+use Psr\Log\LogLevel;
/**
* @group Session
'cookieOptions' => array( 'prefix' => 'x' ),
);
$provider = new CookieSessionProvider( $params );
- $provider->setLogger( new \TestLogger() );
+ $logger = new \TestLogger( true );
+ $provider->setLogger( $logger );
$provider->setConfig( $this->getConfig() );
$provider->setManager( new SessionManager() );
$request = new \FauxRequest();
$info = $provider->provideSessionInfo( $request );
$this->assertNull( $info );
+ $this->assertSame( array(), $logger->getBuffer() );
+ $logger->clearBuffer();
// Session key only
$request = new \FauxRequest();
$this->assertSame( 0, $info->getUserInfo()->getId() );
$this->assertNull( $info->getUserInfo()->getName() );
$this->assertFalse( $info->forceHTTPS() );
+ $this->assertSame( array(
+ array(
+ LogLevel::DEBUG,
+ 'Session "{session}" requested without UserID cookie',
+ ),
+ ), $logger->getBuffer() );
+ $logger->clearBuffer();
// User, no session key
$request = new \FauxRequest();
$this->assertSame( $id, $info->getUserInfo()->getId() );
$this->assertSame( $name, $info->getUserInfo()->getName() );
$this->assertFalse( $info->forceHTTPS() );
+ $this->assertSame( array(), $logger->getBuffer() );
+ $logger->clearBuffer();
// User and session key
$request = new \FauxRequest();
$this->assertSame( $id, $info->getUserInfo()->getId() );
$this->assertSame( $name, $info->getUserInfo()->getName() );
$this->assertFalse( $info->forceHTTPS() );
+ $this->assertSame( array(), $logger->getBuffer() );
+ $logger->clearBuffer();
// User with bad token
$request = new \FauxRequest();
), '' );
$info = $provider->provideSessionInfo( $request );
$this->assertNull( $info );
+ $this->assertSame( array(
+ array(
+ LogLevel::WARNING,
+ 'Session "{session}" requested with invalid Token cookie.'
+ ),
+ ), $logger->getBuffer() );
+ $logger->clearBuffer();
// User id with no token
$request = new \FauxRequest();
$this->assertSame( $id, $info->getUserInfo()->getId() );
$this->assertSame( $name, $info->getUserInfo()->getName() );
$this->assertFalse( $info->forceHTTPS() );
+ $this->assertSame( array(), $logger->getBuffer() );
+ $logger->clearBuffer();
$request = new \FauxRequest();
$request->setCookies( array(
), '' );
$info = $provider->provideSessionInfo( $request );
$this->assertNull( $info );
+ $this->assertSame( array(), $logger->getBuffer() );
+ $logger->clearBuffer();
// User and session key, with forceHTTPS flag
$request = new \FauxRequest();
$this->assertSame( $id, $info->getUserInfo()->getId() );
$this->assertSame( $name, $info->getUserInfo()->getName() );
$this->assertTrue( $info->forceHTTPS() );
+ $this->assertSame( array(), $logger->getBuffer() );
+ $logger->clearBuffer();
// Invalid user id
$request = new \FauxRequest();
), '' );
$info = $provider->provideSessionInfo( $request );
$this->assertNull( $info );
+ $this->assertSame( array(), $logger->getBuffer() );
+ $logger->clearBuffer();
// User id with matching name
$request = new \FauxRequest();
$this->assertSame( $id, $info->getUserInfo()->getId() );
$this->assertSame( $name, $info->getUserInfo()->getName() );
$this->assertFalse( $info->forceHTTPS() );
+ $this->assertSame( array(), $logger->getBuffer() );
+ $logger->clearBuffer();
// User id with wrong name
$request = new \FauxRequest();
), '' );
$info = $provider->provideSessionInfo( $request );
$this->assertNull( $info );
+ $this->assertSame( array(
+ array(
+ LogLevel::WARNING,
+ 'Session "{session}" requested with mismatched UserID and UserName cookies.',
+ ),
+ ), $logger->getBuffer() );
+ $logger->clearBuffer();
}
public function testGetVaryCookies() {
$store = new TestBagOStuff();
$logger = new \TestLogger( true, function ( $m ) {
- return preg_match( '/^SessionBackend a{32} /', $m ) ? null : $m;
+ // Discard all log events starting with expected prefix
+ return preg_match( '/^SessionBackend "\{session\}" /', $m ) ? null : $m;
} );
$manager = new SessionManager( array(
'store' => $store,
namespace MediaWiki\Session;
-use Psr\Log\LogLevel;
+use AuthPlugin;
use MediaWikiTestCase;
+use Psr\Log\LogLevel;
use User;
/**
'Bar' => array( 'X', 'Bar1', 3 => 'Bar2' ),
'Quux' => array( 'Quux' ),
'Baz' => array(),
- 'Quux' => array( 'Quux' ),
);
$this->assertEquals( $expect, $manager->getVaryHeaders() );
\ObjectCache::$instances[__METHOD__] = new TestBagOStuff();
$this->setMwGlobals( array( 'wgMainCacheType' => __METHOD__ ) );
+ $this->setMWGlobals( array(
+ 'wgAuth' => new AuthPlugin,
+ ) );
$this->stashMwGlobals( array( 'wgGroupPermissions' ) );
$wgGroupPermissions['*']['createaccount'] = true;
return null;
}
$m = str_replace( 'MediaWiki\Session\SessionManager::autoCreateUser: ', '', $m );
- $m = preg_replace( '/ - from: .*$/', ' - from: XXX', $m );
return $m;
} );
$manager->setLogger( $logger );
$user->getId(), User::idFromName( 'UTSessionAutoCreate1', User::READ_LATEST )
);
$this->assertSame( array(
- array( LogLevel::INFO, 'creating new user (UTSessionAutoCreate1) - from: XXX' ),
+ array( LogLevel::INFO, 'creating new user ({username}) - from: {url}' ),
), $logger->getBuffer() );
$logger->clearBuffer();
$this->assertEquals( 0, User::idFromName( 'UTDoesNotExist', User::READ_LATEST ) );
$session->clear();
$this->assertSame( array(
- array( LogLevel::DEBUG, 'user is blocked from this wiki, blacklisting' ),
+ array(
+ LogLevel::DEBUG,
+ 'user is blocked from this wiki, blacklisting',
+ ),
), $logger->getBuffer() );
$logger->clearBuffer();
$user->getId(), User::idFromName( 'UTSessionAutoCreate2', User::READ_LATEST )
);
$this->assertSame( array(
- array( LogLevel::INFO, 'creating new user (UTSessionAutoCreate2) - from: XXX' ),
+ array( LogLevel::INFO, 'creating new user ({username}) - from: {url}' ),
), $logger->getBuffer() );
$logger->clearBuffer();
$user->getId(), User::idFromName( 'UTSessionAutoCreate3', User::READ_LATEST )
);
$this->assertSame( array(
- array( LogLevel::INFO, 'creating new user (UTSessionAutoCreate3) - from: XXX' ),
+ array( LogLevel::INFO, 'creating new user ({username}) - from: {url}' ),
), $logger->getBuffer() );
$logger->clearBuffer();
'LocalUserCreated' => array(),
) );
$this->assertSame( array(
- array( LogLevel::INFO, 'creating new user (UTSessionAutoCreate4) - from: XXX' ),
+ array( LogLevel::INFO, 'creating new user ({username}) - from: {url}' ),
), $logger->getBuffer() );
$logger->clearBuffer();
}
$this->objectCacheDef( $provider1 ),
) );
- $user = User::newFromName( 'UTSysop' );
- $token = $user->getToken( true );
-
$this->assertFalse( $manager->isUserSessionPrevented( 'UTSysop' ) );
$manager->preventSessionsForUser( 'UTSysop' );
- $this->assertNotEquals( $token, User::newFromName( 'UTSysop' )->getToken() );
$this->assertTrue( $manager->isUserSessionPrevented( 'UTSysop' ) );
}
public function testLoadSessionInfoFromStore() {
$manager = $this->getManager();
- $logger = new \TestLogger( true, function ( $m ) {
- return preg_replace(
- '/^Session \[\d+\]\w+<(?:null|anon|[+-]:\d+:\w+)>\w+: /', 'Session X: ', $m
- );
- } );
+ $logger = new \TestLogger( true );
$manager->setLogger( $logger );
$request = new \FauxRequest();
$this->assertSame( $unverifiedUserInfo, $info->getUserInfo() );
$this->assertFalse( $loadSessionInfoFromStore( $info ) );
$this->assertSame( array(
- array( LogLevel::WARNING, 'Session X: Unverified user provided and no metadata to auth it' )
+ array(
+ LogLevel::WARNING,
+ 'Session "{session}": Unverified user provided and no metadata to auth it',
+ )
), $logger->getBuffer() );
$logger->clearBuffer();
) );
$this->assertFalse( $loadSessionInfoFromStore( $info ) );
$this->assertSame( array(
- array( LogLevel::WARNING, 'Session X: Null provider and no metadata' ),
+ array( LogLevel::WARNING, 'Session "{session}": Null provider and no metadata' ),
), $logger->getBuffer() );
$logger->clearBuffer();
$this->assertFalse( $info->isIdSafe(), 'sanity check' );
$this->assertFalse( $loadSessionInfoFromStore( $info ) );
$this->assertSame( array(
- array( LogLevel::INFO, 'Session X: No user provided and provider cannot set user' )
+ array( LogLevel::INFO, 'Session "{session}": No user provided and provider cannot set user' )
), $logger->getBuffer() );
$logger->clearBuffer();
$this->store->setRawSession( $id, true );
$this->assertFalse( $loadSessionInfoFromStore( $info ) );
$this->assertSame( array(
- array( LogLevel::WARNING, 'Session X: Bad data' ),
+ array( LogLevel::WARNING, 'Session "{session}": Bad data' ),
), $logger->getBuffer() );
$logger->clearBuffer();
$this->store->setRawSession( $id, array( 'data' => array() ) );
$this->assertFalse( $loadSessionInfoFromStore( $info ) );
$this->assertSame( array(
- array( LogLevel::WARNING, 'Session X: Bad data structure' ),
+ array( LogLevel::WARNING, 'Session "{session}": Bad data structure' ),
), $logger->getBuffer() );
$logger->clearBuffer();
$this->store->setRawSession( $id, array( 'metadata' => $metadata ) );
$this->assertFalse( $loadSessionInfoFromStore( $info ) );
$this->assertSame( array(
- array( LogLevel::WARNING, 'Session X: Bad data structure' ),
+ array( LogLevel::WARNING, 'Session "{session}": Bad data structure' ),
), $logger->getBuffer() );
$logger->clearBuffer();
$this->store->setRawSession( $id, array( 'metadata' => $metadata, 'data' => true ) );
$this->assertFalse( $loadSessionInfoFromStore( $info ) );
$this->assertSame( array(
- array( LogLevel::WARNING, 'Session X: Bad data structure' ),
+ array( LogLevel::WARNING, 'Session "{session}": Bad data structure' ),
), $logger->getBuffer() );
$logger->clearBuffer();
$this->store->setRawSession( $id, array( 'metadata' => true, 'data' => array() ) );
$this->assertFalse( $loadSessionInfoFromStore( $info ) );
$this->assertSame( array(
- array( LogLevel::WARNING, 'Session X: Bad data structure' ),
+ array( LogLevel::WARNING, 'Session "{session}": Bad data structure' ),
), $logger->getBuffer() );
$logger->clearBuffer();
$this->store->setRawSession( $id, array( 'metadata' => $tmp, 'data' => array() ) );
$this->assertFalse( $loadSessionInfoFromStore( $info ) );
$this->assertSame( array(
- array( LogLevel::WARNING, 'Session X: Bad metadata' ),
+ array( LogLevel::WARNING, 'Session "{session}": Bad metadata' ),
), $logger->getBuffer() );
$logger->clearBuffer();
}
) );
$this->assertFalse( $loadSessionInfoFromStore( $info ) );
$this->assertSame( array(
- array( LogLevel::WARNING, 'Session X: Wrong provider, Bad !== Mock' ),
+ array( LogLevel::WARNING, 'Session "{session}": Wrong provider Bad !== Mock' ),
), $logger->getBuffer() );
$logger->clearBuffer();
) );
$this->assertFalse( $loadSessionInfoFromStore( $info ) );
$this->assertSame( array(
- array( LogLevel::WARNING, 'Session X: Unknown provider, Bad' ),
+ array( LogLevel::WARNING, 'Session "{session}": Unknown provider Bad' ),
), $logger->getBuffer() );
$logger->clearBuffer();
) );
$this->assertFalse( $loadSessionInfoFromStore( $info ) );
$this->assertSame( array(
- array( LogLevel::ERROR, 'Session X: Invalid ID' ),
+ array( LogLevel::ERROR, 'Session "{session}": {exception}' ),
), $logger->getBuffer() );
$logger->clearBuffer();
) );
$this->assertFalse( $loadSessionInfoFromStore( $info ) );
$this->assertSame( array(
- array( LogLevel::ERROR, 'Session X: Invalid user name' ),
+ array( LogLevel::ERROR, 'Session "{session}": {exception}', ),
), $logger->getBuffer() );
$logger->clearBuffer();
) );
$this->assertFalse( $loadSessionInfoFromStore( $info ) );
$this->assertSame( array(
- array( LogLevel::WARNING, 'Session X: User ID mismatch, 2 !== 1' ),
+ array( LogLevel::WARNING, 'Session "{session}": User ID mismatch, {uid_a} !== {uid_b}' ),
), $logger->getBuffer() );
$logger->clearBuffer();
) );
$this->assertFalse( $loadSessionInfoFromStore( $info ) );
$this->assertSame( array(
- array( LogLevel::WARNING, 'Session X: User name mismatch, X !== UTSysop' ),
+ array( LogLevel::WARNING, 'Session "{session}": User name mismatch, {uname_a} !== {uname_b}' ),
), $logger->getBuffer() );
$logger->clearBuffer();
$this->assertFalse( $loadSessionInfoFromStore( $info ) );
$this->assertSame( array(
array(
- LogLevel::WARNING, 'Session X: User ID matched but name didn\'t (rename?), X !== UTSysop'
+ LogLevel::WARNING,
+ 'Session "{session}": User ID matched but name didn\'t (rename?), {uname_a} !== {uname_b}'
),
), $logger->getBuffer() );
$logger->clearBuffer();
$this->assertFalse( $loadSessionInfoFromStore( $info ) );
$this->assertSame( array(
array(
- LogLevel::WARNING, 'Session X: Metadata has an anonymous user, but a non-anon user was provided'
+ LogLevel::WARNING,
+ 'Session "{session}": Metadata has an anonymous user, ' .
+ 'but a non-anon user was provided',
),
), $logger->getBuffer() );
$logger->clearBuffer();
) );
$this->assertFalse( $loadSessionInfoFromStore( $info ) );
$this->assertSame( array(
- array( LogLevel::WARNING, 'Session X: User token mismatch' ),
+ array( LogLevel::WARNING, 'Session "{session}": User token mismatch' ),
), $logger->getBuffer() );
$logger->clearBuffer();
) );
$this->assertFalse( $loadSessionInfoFromStore( $info ) );
$this->assertSame( array(
- array( LogLevel::WARNING, 'Session X: Metadata merge failed: no merge!' ),
+ array(
+ LogLevel::WARNING,
+ 'Session "{session}": Metadata merge failed: {exception}',
+ ),
), $logger->getBuffer() );
$logger->clearBuffer();
$this->assertFalse( $loadSessionInfoFromStore( $info ) );
$this->assertTrue( $called );
$this->assertSame( array(
- array( LogLevel::WARNING, 'Session X: Hook aborted' ),
+ array( LogLevel::WARNING, 'Session "{session}": Hook aborted' ),
), $logger->getBuffer() );
$logger->clearBuffer();
}
exit( 1 );
}
+echo defined( 'HHVM_VERSION' ) ?
+ 'Using HHVM ' . HHVM_VERSION . ' (' . PHP_VERSION . ")\n" :
+ 'Using PHP ' . PHP_VERSION . "\n";
+
PHPUnit_TextUI_Command::main();